{
  "openapi": "3.1.0",
  "info": {
    "title": "AbuseGraph API",
    "version": "1.0.0",
    "summary": "Signup risk scoring and exposure monitoring",
    "description": "Score signup and login risk. You get a score, verdict, and plain-language reasons — your app decides what to enforce.\n\n## Quick configure (server check)\nMinimum env for a backend:\n\n```\nABUSEGRAPH_SECRET_KEY=sk_test_…\nABUSEGRAPH_SITE=yourdomain.com\nABUSEGRAPH_CHECK_URL=https://abusegraph.com/api/v1/check\n```\n\n```bash\ncurl -X POST https://abusegraph.com/api/v1/check \\\n  -H \"x-api-key: $ABUSEGRAPH_SECRET_KEY\" \\\n  -H \"x-abusegraph-site: yourdomain.com\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\"email\":\"user@example.com\",\"event\":\"signup\",\"site\":\"yourdomain.com\"}'\n```\n\n| | Test (`sk_test_…`) | Live (`sk_live_…`) |\n|---|---|---|\n| Header `x-api-key` | required | required |\n| Header `x-abusegraph-site` | optional (can omit) | required — licensed hostname |\n| Quota | unlimited test | plan live checks |\n\n## Browser SDK (recommended)\n1. Put a **publishable** key in the browser (`pk_test_…` / `pk_live_…`)\n2. SDK calls `https://api.abusegraph.com` (default — no config needed)\n3. Your server finishes with `POST /api/v1/check` + secret key — forward collect fields, `sessionId`, and/or `tlsCapture`\n\n```\nNEXT_PUBLIC_ABUSEGRAPH_PUBLISHABLE_KEY=pk_test_…\n# Optional: NEXT_PUBLIC_ABUSEGRAPH_EDGE_URL=https://api.abusegraph.com\n```\n\n## Try it here\n1. [Create a free account](https://abusegraph.com/signup) → finish setup → copy `sk_test_…`\n2. Paste under **Authentication** → open **Risk Check** → **Test Request**\n3. Prefer test keys in this browser; they are not saved across reloads\n\n## Auth cheat sheet\n| Key | Where | Header | Routes |\n|---|---|---|---|\n| `sk_…` secret | your server only | `x-api-key` (or `Authorization: Bearer` on Monitor) | `/api/v1/*` on abusegraph.com |\n| `pk_…` publishable | browser / SDK | `x-api-key` or JSON `publicKey` | `/v1/risk/evaluate` on api.abusegraph.com |\n\nNever put secret keys in frontend bundles or git.\n\n## Response fields (check / evaluate)\n- **score** (0–100) — overall risk (`scores.overall` mirrors it)\n- **verdict** — `allow` | `challenge` | `shadow` | `block`\n- **scores** / **flags** — facet scores and boolean rollups\n- **tls** — always present (JA3/JA4 + capture status); `expand=tls` deepens it\n- **network** — always present (`kind` + optional channel)\n- **risk** / **riskSignals** / **reasons** — why the score moved\n- **recommendation** — suggested action (check)\n- **requestId** — also on `X-Request-Id`\n\n## Errors & limits\nErrors use `application/problem+json` (RFC 9457). Rate headers:\n`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`.\n\n## Guides\n- [Browser SDK](https://abusegraph.com/how-it-works#sdk)\n- [Integration guides](https://abusegraph.com/docs/guides)\n- [AI agent prompt](https://abusegraph.com/agents) — also [llms.txt](https://abusegraph.com/llms.txt)\n- [OpenAPI JSON](https://abusegraph.com/openapi.json)\n- [Console keys](https://abusegraph.com/app/settings/keys)",
    "contact": {
      "name": "AbuseGraph",
      "url": "https://abusegraph.com",
      "email": "hello@abusegraph.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://abusegraph.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://abusegraph.com",
      "description": "Server API — secret key (sk_…)"
    },
    {
      "url": "https://api.abusegraph.com",
      "description": "Browser SDK — publishable key (pk_…)"
    }
  ],
  "tags": [
    {
      "name": "Risk Check",
      "description": "Server-side risk scoring with your secret API key."
    },
    {
      "name": "Account",
      "description": "Pull plan, usage, domains, and key metadata with your secret API key."
    },
    {
      "name": "Monitor",
      "description": "Daily exposure monitoring for domains you own and people you name. Three layers: employees on your systems and elsewhere (DNS-verified company domain), and users of your app (licensed app login host, e.g. app.acme.com). Alerts report where credentials appeared — never passwords. No auto-enroll."
    },
    {
      "name": "Email Intelligence",
      "description": "Standalone email risk scoring (disposable, domain, exposure) without a full check."
    },
    {
      "name": "Session",
      "description": "Create a short-lived sessionId from collect, then bind an account on check."
    },
    {
      "name": "Linked Accounts",
      "description": "Query the identity graph for multi-account neighbors."
    },
    {
      "name": "Browser Evaluate",
      "description": "Preliminary browser evaluation used by the AbuseGraph SDK (publishable key)."
    }
  ],
  "components": {
    "securitySchemes": {
      "SecretApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Workspace secret key (`sk_test_…` / `sk_live_…`). Prefer test keys in these docs. Create keys in the console — never commit real values."
      },
      "BearerSecretKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Same secret as `x-api-key`, accepted on Monitor as `Authorization: Bearer sk_…`."
      },
      "PublishableApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Publishable key (`pk_test_…` / `pk_live_…`). Safe in the browser. The SDK sends this automatically; you can also pass `publicKey` in the JSON body."
      }
    },
    "parameters": {
      "RequestId": {
        "name": "X-Request-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "description": "Optional client correlation id. Echoed on the response; minted when omitted."
      },
      "Expand": {
        "name": "expand",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "example": "tls"
        },
        "description": "Comma-separated expand flags that change response shaping: `tls`, `linked`, `all`. Compact `tls` and `network` are always present on check/evaluate success. `expand=tls` (or `all`) deepens ClientHello fields on `tls`. `expand=linked` (or `all`) returns `linkedAccounts` when neighbors are available. `detection` / `recommendation` / `compare` are emitted when the engine produces them (not gated by expand)."
      },
      "SiteHeader": {
        "name": "x-abusegraph-site",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Licensed site hostname for **live** secret-key checks. Leave blank when trying `sk_test_…` from these docs. Required for `sk_live_…`."
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "error"
        ],
        "description": "RFC 9457 Problem Details (`application/problem+json`). Match on `type` / `error`; read `detail` for this occurrence. Validation failures may include both `issues` (field map) and `errors` (pointer list).",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "example": "https://abusegraph.com/problems/invalid-api-key"
          },
          "title": {
            "type": "string",
            "example": "Unauthorized"
          },
          "status": {
            "type": "integer",
            "example": 401
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string",
            "example": "/api/v1/check"
          },
          "error": {
            "type": "string",
            "description": "Legacy machine-readable code (snake_case).",
            "example": "invalid_api_key"
          },
          "requestId": {
            "type": "string"
          },
          "retryAfterSec": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "remaining": {
            "type": "integer"
          },
          "issues": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Zod-style field → messages map (legacy)."
          },
          "errors": {
            "type": "array",
            "description": "RFC/Zuplo-style validation errors (derived from `issues` when present).",
            "items": {
              "type": "object",
              "required": [
                "detail",
                "pointer"
              ],
              "properties": {
                "detail": {
                  "type": "string"
                },
                "pointer": {
                  "type": "string",
                  "example": "#/email",
                  "description": "JSON Pointer into the request body."
                }
              }
            }
          }
        }
      },
      "Verdict": {
        "type": "string",
        "enum": [
          "allow",
          "challenge",
          "shadow",
          "block"
        ],
        "description": "Risk decision for the request. `allow` — proceed; `challenge` — step-up (MFA / captcha); `shadow` — allow but flag for review; `block` — reject."
      },
      "RecommendationAction": {
        "type": "string",
        "enum": [
          "allow",
          "challenge",
          "shadow",
          "block",
          "review"
        ],
        "description": "Recommended product action. Includes `review` (manual queue) in addition to score verdicts."
      },
      "NetworkKind": {
        "type": "string",
        "enum": [
          "clean",
          "vpn",
          "tor",
          "datacenter",
          "wifi",
          "proxy",
          "abuse",
          "mixed"
        ],
        "description": "Safe network rollup from detection signals."
      },
      "ReferrerChannel": {
        "type": "string",
        "enum": [
          "direct",
          "search",
          "social",
          "email",
          "paid",
          "internal",
          "referral"
        ],
        "description": "Landing / referrer channel when attribution is known."
      },
      "TlsConsistencyStatus": {
        "type": "string",
        "enum": [
          "aligned",
          "mismatch",
          "strong_mismatch",
          "unknown"
        ],
        "description": "UA ↔ TLS browser-stack consistency (only with `?expand=tls`)."
      },
      "CompartmentSeverity": {
        "type": "string",
        "enum": [
          "none",
          "low",
          "medium",
          "high"
        ]
      },
      "NetworkSummary": {
        "type": "object",
        "required": [
          "kind"
        ],
        "description": "Always present on check/evaluate. Safe network rollup from fired signals.",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/NetworkKind"
          },
          "channel": {
            "$ref": "#/components/schemas/ReferrerChannel"
          },
          "paid": {
            "type": "boolean",
            "description": "True when attribution indicates paid traffic."
          },
          "trueCountry": {
            "type": "string",
            "description": "Best-effort ISO-3166 alpha-2 when geo signals agree or after spoof detection."
          },
          "locationSpoof": {
            "type": "boolean",
            "description": "True when timezone/language geo mismatches suggest spoofed location."
          }
        }
      },
      "RiskFlags": {
        "type": "object",
        "required": [
          "vpn",
          "tor",
          "datacenter",
          "proxy",
          "abuseIp",
          "disposableEmail",
          "bot",
          "multiAccount",
          "velocity",
          "breach"
        ],
        "description": "Boolean risk rollups derived from fired signals. Derived from fired signals + network kind — never invented.",
        "properties": {
          "vpn": {
            "type": "boolean",
            "description": "VPN ASN / network kind."
          },
          "tor": {
            "type": "boolean",
            "description": "Tor exit detected."
          },
          "datacenter": {
            "type": "boolean",
            "description": "Datacenter / hosting ASN."
          },
          "proxy": {
            "type": "boolean",
            "description": "Corporate / known proxy."
          },
          "abuseIp": {
            "type": "boolean",
            "description": "IP on abuse / threat lists."
          },
          "disposableEmail": {
            "type": "boolean",
            "description": "Throwaway / disposable email domain."
          },
          "bot": {
            "type": "boolean",
            "description": "Automation / bot signals fired."
          },
          "multiAccount": {
            "type": "boolean",
            "description": "Shared device/IP / multi-account pattern."
          },
          "velocity": {
            "type": "boolean",
            "description": "Realtime velocity threshold exceeded."
          },
          "breach": {
            "type": "boolean",
            "description": "Breach or credential-exposure hit."
          }
        }
      },
      "ScoreFacets": {
        "type": "object",
        "required": [
          "overall",
          "bot",
          "network",
          "email",
          "identity",
          "tls",
          "multipleAccounts",
          "riskSignals"
        ],
        "description": "Facet scores from 0–100 across risk dimensions. `overall` matches top-level `score`. Facets derive from detection severity.",
        "properties": {
          "overall": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Same as top-level `score`."
          },
          "bot": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Automation / bot facet."
          },
          "network": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "VPN / Tor / datacenter / abuse facet."
          },
          "email": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Email risk facet."
          },
          "identity": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Identity graph / multi-account / breach facet."
          },
          "tls": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "TLS fingerprint / consistency facet."
          },
          "multipleAccounts": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Multi-accounting / account-linking risk."
          },
          "riskSignals": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Aggregate non-bot risk (network, email, TLS, breach, …)."
          }
        }
      },
      "EmailSummary": {
        "type": "object",
        "required": [
          "disposable",
          "risky",
          "breach"
        ],
        "description": "Present when email risk signals fired.",
        "properties": {
          "disposable": {
            "type": "boolean",
            "description": "Disposable / throwaway domain."
          },
          "risky": {
            "type": "boolean",
            "description": "Any elevated email risk (disposable, pattern, breach)."
          },
          "breach": {
            "type": "boolean",
            "description": "Known breach or credential exposure."
          }
        }
      },
      "DetectionPackView": {
        "type": "object",
        "required": [
          "version",
          "summary",
          "categories",
          "compartments",
          "topReasons",
          "unusual",
          "network",
          "device"
        ],
        "description": "Holistic, consumer-safe detection pack (no weights, hashes, or vendor IDs). Returned on check/evaluate/signal when the engine builds one.",
        "properties": {
          "version": {
            "type": "integer",
            "enum": [
              1
            ],
            "description": "Pack schema version."
          },
          "summary": {
            "type": "string",
            "description": "Plain-language rollup of what fired."
          },
          "categories": {
            "type": "array",
            "description": "Founder-facing category hits for UI grouping.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "label",
                "hit"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "example": "risky_email"
                },
                "label": {
                  "type": "string",
                  "example": "Risky email"
                },
                "hit": {
                  "type": "boolean"
                }
              }
            }
          },
          "compartments": {
            "type": "array",
            "description": "Technical compartments inspected (automation, network, tls, …).",
            "items": {
              "type": "object",
              "required": [
                "id",
                "label",
                "severity",
                "hit",
                "reasons",
                "count"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "example": "email",
                  "description": "Compartment id: automation|fingerprint|behavioral|network|identity|email|breach|visit|attribution|tls|ua|extension."
                },
                "label": {
                  "type": "string",
                  "example": "Email"
                },
                "severity": {
                  "$ref": "#/components/schemas/CompartmentSeverity"
                },
                "hit": {
                  "type": "boolean"
                },
                "reasons": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Consumer-safe reason codes in this compartment."
                },
                "count": {
                  "type": "integer",
                  "description": "Number of reasons in this compartment."
                }
              }
            }
          },
          "topReasons": {
            "type": "array",
            "description": "Top consumer reason codes with labels.",
            "items": {
              "type": "object",
              "required": [
                "type",
                "label",
                "category"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "example": "disposable_email"
                },
                "label": {
                  "type": "string",
                  "example": "Disposable email"
                },
                "category": {
                  "type": "string",
                  "example": "risky_email"
                }
              }
            }
          },
          "unusual": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Opaque visit-drift codes (e.g. new_device, score_spike)."
          },
          "network": {
            "$ref": "#/components/schemas/NetworkSummary"
          },
          "device": {
            "type": "object",
            "required": [
              "automation",
              "confidence"
            ],
            "description": "Device / automation rollup.",
            "properties": {
              "automation": {
                "type": "string",
                "enum": [
                  "none",
                  "low",
                  "medium",
                  "high"
                ]
              },
              "confidence": {
                "type": "string",
                "enum": [
                  "low",
                  "medium",
                  "high",
                  "unknown"
                ]
              }
            }
          }
        }
      },
      "CompareDelta": {
        "type": "object",
        "required": [
          "compartment",
          "code",
          "label",
          "detail"
        ],
        "description": "One difference vs prior visits for this identity.",
        "properties": {
          "compartment": {
            "type": "string",
            "example": "fingerprint"
          },
          "code": {
            "type": "string",
            "example": "visit_new_device"
          },
          "label": {
            "type": "string",
            "example": "New device"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "CompareSummary": {
        "type": "object",
        "required": [
          "priorCount",
          "firstSeen",
          "deltas"
        ],
        "description": "Prior-visit comparison when history exists for this visitor/email.",
        "properties": {
          "priorCount": {
            "type": "integer",
            "description": "Number of prior visits matched."
          },
          "firstSeen": {
            "type": "boolean",
            "description": "True when this is the first observed visit."
          },
          "deltas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CompareDelta"
            }
          }
        }
      },
      "Recommendation": {
        "type": "object",
        "required": [
          "action",
          "title",
          "detail"
        ],
        "description": "Product recommendation for the console / your enforcement UX.",
        "properties": {
          "action": {
            "$ref": "#/components/schemas/RecommendationAction"
          },
          "title": {
            "type": "string",
            "example": "Challenge this signup"
          },
          "detail": {
            "type": "string",
            "description": "Short explanation of why this action is suggested."
          },
          "bullets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Key reasons as short bullets."
          },
          "compare": {
            "type": "string",
            "description": "Optional one-line compare summary."
          },
          "priorCount": {
            "type": "integer"
          },
          "deltas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CompareDelta"
            }
          }
        }
      },
      "SignalResponse": {
        "type": "object",
        "required": [
          "score",
          "verdict",
          "riskSignals"
        ],
        "description": "Internal enrichment response shape (not a public route). Listed for schema completeness only — call `POST /api/v1/check` instead.",
        "properties": {
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Final risk score (0–100)."
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "riskSignals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskReason"
            },
            "description": "Fully enriched consumer-safe reason codes."
          },
          "detection": {
            "$ref": "#/components/schemas/DetectionPackView"
          }
        }
      },
      "MonitorEnrollResult": {
        "type": "object",
        "required": [
          "emailMasked",
          "ok"
        ],
        "properties": {
          "emailMasked": {
            "type": "string",
            "example": "u***@example.com",
            "description": "Masked email (PII-safe)."
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identity id when enrollment succeeded."
          },
          "ok": {
            "type": "boolean"
          },
          "error": {
            "type": "string",
            "description": "Present when ok=false, or soft note like `updated` when ok=true."
          }
        }
      },
      "TlsFingerprint": {
        "type": "object",
        "required": [
          "ja3",
          "ja4"
        ],
        "description": "Optional TLS handshake fingerprint captured by the browser SDK (JA3/JA4 + structural fields).",
        "properties": {
          "ja3": {
            "type": "string"
          },
          "ja4": {
            "type": "string"
          },
          "ja3String": {
            "type": "string"
          },
          "tlsVersion": {
            "type": "string"
          },
          "clientTlsVersion": {
            "type": "string"
          },
          "alpn": {
            "type": "string"
          },
          "cipherSuite": {
            "type": "string"
          },
          "sni": {
            "type": "string"
          },
          "hasSni": {
            "type": "boolean"
          },
          "clientAlpn": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "supportedGroupsN": {
            "type": "integer"
          },
          "cipherSuitesN": {
            "type": "integer"
          },
          "extensionsN": {
            "type": "integer"
          },
          "supportedGroups": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Supported groups as 4-digit hex"
          },
          "extensions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "TLS extensions as 4-digit hex"
          },
          "signatureAlgorithms": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signature algorithms as 4-digit hex"
          },
          "supportedPoints": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "EC point formats as 2-digit hex"
          },
          "cipherSuites": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Offered cipher suites as 4-digit hex (order preserved)"
          },
          "greaseN": {
            "type": "integer"
          },
          "hasGrease": {
            "type": "boolean"
          },
          "hasGreaseCipherSuite": {
            "type": "boolean"
          },
          "hasGreaseExtension": {
            "type": "boolean"
          },
          "hasGreaseGroup": {
            "type": "boolean"
          },
          "greaseCipherSuitesN": {
            "type": "integer"
          },
          "greaseExtensionsN": {
            "type": "integer"
          },
          "greaseGroupsN": {
            "type": "integer"
          },
          "supportedVersions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "supported_versions as 4-digit hex"
          },
          "keyShareGroups": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "key_share named groups as 4-digit hex"
          },
          "hasEch": {
            "type": "boolean"
          },
          "hasAlps": {
            "type": "boolean"
          },
          "hasCertCompression": {
            "type": "boolean"
          },
          "hasPadding": {
            "type": "boolean"
          },
          "hasPsk": {
            "type": "boolean"
          },
          "hasSessionTicket": {
            "type": "boolean"
          },
          "hasDelegatedCredentials": {
            "type": "boolean"
          },
          "hasRecordSizeLimit": {
            "type": "boolean"
          },
          "cipherOrderHash": {
            "type": "string"
          },
          "extensionOrderHash": {
            "type": "string"
          },
          "extensionSetHash": {
            "type": "string"
          },
          "tlsExactHash": {
            "type": "string"
          },
          "tlsFamilyHash": {
            "type": "string"
          },
          "tlsProfile": {
            "type": "object",
            "additionalProperties": true,
            "description": "UA↔TLS consistency classification (server-enriched)."
          },
          "captureToken": {
            "type": "string",
            "description": "Optional signed capture token (prefer top-level tlsCapture)."
          }
        }
      },
      "TlsCapture": {
        "type": "object",
        "required": [
          "version",
          "token"
        ],
        "description": "Optional signed TLS capture envelope. Preferred over embedding captureToken on tlsFingerprint.",
        "properties": {
          "version": {
            "type": "integer",
            "enum": [
              2
            ]
          },
          "token": {
            "type": "string"
          },
          "issuedAt": {
            "type": "integer"
          },
          "expiresAt": {
            "type": "integer"
          },
          "nonce": {
            "type": "string"
          }
        }
      },
      "TlsCaptureStatus": {
        "type": "string",
        "enum": [
          "verified",
          "missing",
          "invalid",
          "expired",
          "replayed",
          "unverified",
          "signing_disabled",
          "client_supplied"
        ],
        "description": "Capture verify status. Evaluate (signed capture): verified, missing, invalid, expired, replayed, unverified, signing_disabled. Check: same statuses when tlsCapture is forwarded, plus client_supplied when JA3/JA4 arrive without a verified token (or from a prior session)."
      },
      "TlsResponseSummary": {
        "type": "object",
        "required": [
          "present",
          "flagged",
          "reasons"
        ],
        "description": "Always present on successful check/evaluate responses. Compact by default; `?expand=tls` adds ClientHello detail fields.",
        "properties": {
          "present": {
            "type": "boolean",
            "description": "True when both JA3 and JA4 fingerprints are available."
          },
          "captureStatus": {
            "$ref": "#/components/schemas/TlsCaptureStatus"
          },
          "flagged": {
            "type": "boolean",
            "description": "True when TLS compartment reasons fired."
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "TLS compartment reason codes (empty when not flagged)."
          },
          "ja3": {
            "type": "string",
            "description": "JA3 hash when a fingerprint was captured."
          },
          "ja4": {
            "type": "string",
            "description": "JA4 hash when a fingerprint was captured."
          },
          "alpn": {
            "type": "string",
            "description": "Negotiated ALPN — present when `?expand=tls`."
          },
          "tlsVersion": {
            "type": "string",
            "description": "TLS version string — present when `?expand=tls`."
          },
          "clientTlsVersion": {
            "type": "string",
            "description": "ClientHello advertised TLS version — present when `?expand=tls`."
          },
          "cipherSuite": {
            "type": "string",
            "description": "Negotiated cipher suite — present when `?expand=tls`."
          },
          "hasSni": {
            "type": "boolean",
            "description": "SNI present in ClientHello — when `?expand=tls`."
          },
          "clientAlpn": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Client ALPN offer list — when `?expand=tls`."
          },
          "supportedGroupsN": {
            "type": "integer",
            "description": "Supported groups count — when `?expand=tls`."
          },
          "cipherSuitesN": {
            "type": "integer",
            "description": "Cipher suites count — when `?expand=tls`."
          },
          "extensionsN": {
            "type": "integer",
            "description": "Extensions count — when `?expand=tls`."
          },
          "supportedGroups": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Supported groups hex list — when `?expand=tls`."
          },
          "extensions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Extensions hex list — when `?expand=tls`."
          },
          "signatureAlgorithms": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signature algorithms hex — when `?expand=tls`."
          },
          "supportedPoints": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "EC point formats hex — when `?expand=tls`."
          },
          "cipherSuites": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Offered cipher suites hex — when `?expand=tls`."
          },
          "hasGrease": {
            "type": "boolean",
            "description": "GREASE observed — present when `?expand=tls`."
          },
          "consistencyStatus": {
            "$ref": "#/components/schemas/TlsConsistencyStatus",
            "description": "UA↔TLS consistency — present when `?expand=tls`."
          }
        }
      },
      "RiskReason": {
        "type": "object",
        "required": [
          "type"
        ],
        "description": "Consumer-safe fired signal (no weights or provider detail).",
        "properties": {
          "type": {
            "type": "string",
            "example": "disposable_email",
            "description": "Machine-readable reason code (snake_case)."
          },
          "label": {
            "type": "string",
            "example": "Disposable email",
            "description": "Human-readable product label (optional)."
          }
        }
      },
      "ReasonPresentation": {
        "type": "object",
        "required": [
          "code",
          "label",
          "category"
        ],
        "description": "UI-ready reason with founder category for grouping.",
        "properties": {
          "code": {
            "type": "string",
            "example": "disposable_email",
            "description": "Same family as RiskReason.type."
          },
          "label": {
            "type": "string",
            "example": "Disposable email"
          },
          "category": {
            "type": "string",
            "example": "risky_email",
            "description": "Founder category id for UI grouping."
          }
        }
      },
      "RiskGroup": {
        "type": "object",
        "required": [
          "signals"
        ],
        "description": "Preferred nested risk payload on check responses.",
        "properties": {
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskReason"
            },
            "description": "Fired signals for this decision."
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReasonPresentation"
            },
            "description": "Presentation-ready reasons (code/label/category)."
          },
          "unusual": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Visit-drift / anomaly codes."
          },
          "summary": {
            "type": "string",
            "description": "Plain-language risk summary (mirrors top-level summary)."
          }
        }
      },
      "CheckRequest": {
        "type": "object",
        "description": "Server-side risk check body. Unknown SDK fields are ignored safely (passthrough). Prefer `sessionId` + account fields, or forward collect probes / `tlsCapture` from the browser SDK.",
        "properties": {
          "email": {
            "type": "string",
            "description": "User email for disposable / breach / graph checks. Empty string allowed.",
            "maxLength": 320,
            "examples": [
              "user@example.com",
              ""
            ]
          },
          "userId": {
            "type": "string",
            "maxLength": 256,
            "description": "Your stable user id (optional)."
          },
          "ip": {
            "type": "string",
            "maxLength": 128,
            "example": "203.0.113.10",
            "description": "Client IP (use the real end-user IP, not your server)."
          },
          "fingerprint": {
            "type": "string",
            "maxLength": 512,
            "description": "Browser visitor id from the SDK / collect payload."
          },
          "fingerprintBase": {
            "type": "string",
            "maxLength": 512,
            "description": "Stable fingerprint base when provided by the SDK."
          },
          "fingerprintConfidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "SDK confidence score for the fingerprint (0–1)."
          },
          "userAgent": {
            "type": "string",
            "maxLength": 1024,
            "description": "Client User-Agent string."
          },
          "event": {
            "type": "string",
            "enum": [
              "signup",
              "login",
              "password_reset",
              "email_change",
              "evaluate"
            ],
            "description": "Auth event being scored."
          },
          "sessionId": {
            "type": "string",
            "maxLength": 128,
            "description": "Session from `POST /api/v1/session` — merges prior collect payload before scoring."
          },
          "phone": {
            "type": "string",
            "maxLength": 32,
            "description": "Optional E.164-ish phone for account-protection heuristics during live auth events (not KYC, list cleaning, or bulk verification)."
          },
          "verdictToken": {
            "type": "string",
            "description": "Optional token from browser evaluate. Accepted for SDK compatibility; enrichment for check comes from collect fields, `sessionId`, and `tlsCapture`."
          },
          "country": {
            "type": "string",
            "maxLength": 8,
            "description": "ISO country when known."
          },
          "city": {
            "type": "string",
            "maxLength": 80
          },
          "region": {
            "type": "string",
            "maxLength": 80
          },
          "asn": {
            "type": "integer",
            "description": "Autonomous system number."
          },
          "asOrganization": {
            "type": "string",
            "maxLength": 200
          },
          "colo": {
            "type": "string",
            "maxLength": 8,
            "description": "Edge colo / PoP code when known."
          },
          "botScore": {
            "type": "integer",
            "minimum": 1,
            "maximum": 99,
            "description": "Bot score from your edge (1–99) when available."
          },
          "verifiedBot": {
            "type": "boolean"
          },
          "corporateProxy": {
            "type": "boolean"
          },
          "site": {
            "type": "string",
            "maxLength": 253,
            "description": "Licensed site hostname (alternative to `x-abusegraph-site` header)."
          },
          "siteOrigin": {
            "type": "string",
            "maxLength": 512,
            "description": "Full origin of the licensed site when available."
          },
          "behavioral": {
            "type": "object",
            "description": "SDK behavioral signals (mouse, typing, …). Opaque object.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "probes": {
            "type": "object",
            "description": "SDK environment / automation probes. Opaque object.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "tlsFingerprint": {
            "$ref": "#/components/schemas/TlsFingerprint"
          },
          "tlsCapture": {
            "$ref": "#/components/schemas/TlsCapture"
          },
          "extensionSet": {
            "type": "object",
            "description": "Browser extension-set signals from the SDK.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "attribution": {
            "type": "object",
            "description": "Landing / referrer / UTM attribution snapshot.",
            "additionalProperties": {
              "type": "object"
            }
          }
        }
      },
      "CheckResponse": {
        "type": "object",
        "required": [
          "score",
          "verdict",
          "scores",
          "flags",
          "accountsLinked",
          "risk",
          "riskSignals",
          "tls",
          "network"
        ],
        "description": "Authoritative risk decision for a signup/login. Always includes actionable `verdict`, facet `scores`, boolean `flags`, `accountsLinked`, nested `risk`, compact `tls`, and `network`. On rare fail-open bypasses, `failOpen: true` may be set and some nested fields omitted.",
        "properties": {
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Overall risk score from 0 (clean) to 100 (highest risk). Mirrored in `scores.overall`."
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "scores": {
            "$ref": "#/components/schemas/ScoreFacets"
          },
          "flags": {
            "$ref": "#/components/schemas/RiskFlags"
          },
          "accountsLinked": {
            "type": "integer",
            "minimum": 0,
            "description": "Count of accounts linked via the identity graph."
          },
          "linkedAccounts": {
            "type": "array",
            "description": "Present when `?expand=linked` (or `all`).",
            "items": {
              "type": "object",
              "required": [
                "accountId",
                "score",
                "matchTypes"
              ],
              "properties": {
                "accountId": {
                  "type": "string"
                },
                "score": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "matchTypes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "labels": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "summary": {
            "type": "string",
            "description": "Plain-language explanation of the recommendation.",
            "example": "Recommend challenge: this signup showed email risk."
          },
          "event": {
            "type": "string",
            "enum": [
              "signup",
              "login",
              "password_reset",
              "email_change",
              "evaluate"
            ],
            "description": "Echo of the scored event when provided."
          },
          "risk": {
            "$ref": "#/components/schemas/RiskGroup"
          },
          "riskSignals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskReason"
            },
            "description": "Flat alias of `risk.signals` for older clients."
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReasonPresentation"
            },
            "description": "Flat alias of `risk.reasons` when present."
          },
          "unusual": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Flat alias of `risk.unusual` when present."
          },
          "recommendation": {
            "$ref": "#/components/schemas/Recommendation"
          },
          "detection": {
            "$ref": "#/components/schemas/DetectionPackView"
          },
          "compare": {
            "$ref": "#/components/schemas/CompareSummary"
          },
          "tls": {
            "$ref": "#/components/schemas/TlsResponseSummary"
          },
          "network": {
            "$ref": "#/components/schemas/NetworkSummary"
          },
          "email": {
            "$ref": "#/components/schemas/EmailSummary"
          },
          "requestId": {
            "type": "string",
            "description": "Correlation id (also returned as `X-Request-Id` header)."
          },
          "failOpen": {
            "type": "boolean",
            "description": "True when the API returned a soft allow because an upstream dependency failed."
          },
          "visitorId": {
            "type": "string",
            "description": "Stable visitor id associated with this decision."
          }
        }
      },
      "EmailIntelResponse": {
        "type": "object",
        "required": [
          "email",
          "domain",
          "recommendation",
          "score",
          "disposable",
          "risky",
          "breach",
          "signals"
        ],
        "description": "Standalone email intelligence result.",
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "domain": {
            "type": "string"
          },
          "recommendation": {
            "type": "string",
            "enum": [
              "send",
              "send_with_caution",
              "do_not_send"
            ]
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "disposable": {
            "type": "boolean"
          },
          "risky": {
            "type": "boolean"
          },
          "breach": {
            "type": "boolean"
          },
          "signals": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "detail"
              ],
              "properties": {
                "type": {
                  "type": "string"
                },
                "detail": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SessionCreateResponse": {
        "type": "object",
        "required": [
          "sessionId",
          "expiresAt"
        ],
        "properties": {
          "sessionId": {
            "type": "string",
            "description": "Pass to `POST /api/v1/check` as `sessionId`."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "UTC expiry (typically 24h)."
          }
        }
      },
      "LinkedAccountsResponse": {
        "type": "object",
        "required": [
          "ok",
          "accountsLinked",
          "highRiskCount",
          "matchTypeCounts",
          "linkedAccounts"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always `true` on success.",
            "example": true
          },
          "accountsLinked": {
            "type": "integer",
            "minimum": 0
          },
          "highRiskCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Neighbors with score ≥ 70, block verdict, or abuse labels."
          },
          "matchTypeCounts": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Edge-type histogram across the cluster."
          },
          "linkedAccounts": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "accountId",
                "score",
                "matchTypes"
              ],
              "properties": {
                "accountId": {
                  "type": "string"
                },
                "score": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "matchTypes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "hops": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 3
                },
                "verdict": {
                  "type": "string",
                  "enum": [
                    "allow",
                    "challenge",
                    "shadow",
                    "block"
                  ]
                },
                "isAbuser": {
                  "type": "boolean"
                },
                "labels": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "AccountResponse": {
        "type": "object",
        "required": [
          "workspace",
          "plan",
          "usage",
          "domains",
          "key",
          "limits",
          "features"
        ],
        "description": "Workspace plan, usage, domains, and key metadata for the calling secret key. Never includes raw key material.",
        "properties": {
          "workspace": {
            "type": "object",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Workspace id."
              },
              "name": {
                "type": "string",
                "description": "Workspace display name."
              }
            }
          },
          "plan": {
            "type": "object",
            "required": [
              "id",
              "name",
              "price",
              "priceCents",
              "status"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "Plan id (`free`, `pro`, `scale`, …)."
              },
              "name": {
                "type": "string",
                "description": "Human-readable plan name."
              },
              "price": {
                "type": "string",
                "description": "Display price string (e.g. `$99/mo`)."
              },
              "priceCents": {
                "type": "integer",
                "description": "List price in USD cents per billing period."
              },
              "status": {
                "type": "string",
                "description": "Subscription status string from billing."
              },
              "trialing": {
                "type": "boolean",
                "description": "True while on trial."
              },
              "trialEndsAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Trial end timestamp when applicable."
              },
              "domainSeats": {
                "type": "integer",
                "description": "Licensed production domain seats on this plan."
              }
            }
          },
          "usage": {
            "type": "object",
            "required": [
              "checksUsed",
              "monthlyChecks",
              "remaining",
              "allowsOverage",
              "liveChecksAllowed"
            ],
            "properties": {
              "checksUsed": {
                "type": "integer",
                "description": "Live checks used in the current period."
              },
              "monthlyChecks": {
                "type": "integer",
                "description": "Included live checks per period."
              },
              "remaining": {
                "type": "integer",
                "description": "Included live checks still available."
              },
              "percentUsed": {
                "type": "number",
                "description": "Percent of included quota consumed."
              },
              "testChecksUsed": {
                "type": "integer",
                "description": "Test-key (`sk_test_`) checks used this period."
              },
              "overageUsed": {
                "type": "integer",
                "description": "Live checks beyond the included quota."
              },
              "overageCents": {
                "type": "integer",
                "description": "Estimated overage charge in USD cents."
              },
              "overageRate": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Display overage rate (e.g. `$4/1k`) or null."
              },
              "allowsOverage": {
                "type": "boolean",
                "description": "Whether the plan allows billable overage."
              },
              "periodStart": {
                "type": "string",
                "description": "Usage period start (ISO date or datetime)."
              },
              "periodEnd": {
                "type": "string",
                "description": "Usage period end (ISO date or datetime)."
              },
              "liveChecksAllowed": {
                "type": "boolean",
                "description": "Whether live secret-key checks are permitted for this workspace."
              },
              "liveApiAccessAllowed": {
                "type": "boolean",
                "description": "Whether `sk_live_` may call `/api/v1/check` (plan allows live credits; Free pauses at quota)."
              }
            }
          },
          "domains": {
            "type": "object",
            "description": "Licensed / verified / staging hostnames.",
            "properties": {
              "licensed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Production hostnames licensed for live keys."
              },
              "verified": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "DNS-verified hostnames."
              },
              "staging": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Staging hostnames allowed for test traffic."
              },
              "max": {
                "type": "integer",
                "description": "Max licensed production domains."
              },
              "seats": {
                "type": "integer",
                "description": "Domain seats available on the plan."
              }
            }
          },
          "key": {
            "type": "object",
            "description": "Calling key metadata only — never the raw secret.",
            "properties": {
              "kind": {
                "type": "string",
                "description": "`secret` for sk_ keys."
              },
              "env": {
                "type": "string",
                "description": "`live` or `test`."
              },
              "prefix": {
                "type": "string",
                "description": "Key prefix for identification (e.g. `sk_live_ab…`)."
              },
              "scope": {
                "type": "string",
                "description": "Key scope (e.g. workspace / site)."
              },
              "websiteHostname": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Bound site hostname when the key is site-scoped."
              }
            }
          },
          "limits": {
            "type": "object",
            "description": "Plan rate limits and caps.",
            "properties": {
              "apiRateLimit": {
                "type": "integer",
                "description": "Console API requests allowed per window."
              },
              "apiRateWindowSec": {
                "type": "integer",
                "description": "Console API rate-limit window in seconds."
              },
              "edgeRateLimit": {
                "type": "integer",
                "description": "Edge evaluate requests allowed per window."
              },
              "edgeRateWindowSec": {
                "type": "integer",
                "description": "Edge rate-limit window in seconds."
              },
              "maxTeamMembers": {
                "type": "integer",
                "description": "Max console seats on this plan."
              },
              "monitoredIdentities": {
                "type": "integer",
                "description": "Max identities that can be enrolled in Monitor."
              }
            }
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Enabled feature flags for this plan/workspace."
          }
        }
      },
      "BreachLocation": {
        "type": "object",
        "required": [
          "domain"
        ],
        "description": "Where credentials appeared — a service host or breach-source label. Never usernames or passwords.",
        "properties": {
          "layer": {
            "type": "string",
            "enum": [
              "employee_on_system",
              "employee_elsewhere",
              "platform_user"
            ],
            "description": "Which protection layer saw it: employees on your systems, employees elsewhere, or users of your app."
          },
          "domain": {
            "type": "string",
            "example": "login.example.net",
            "description": "Service / login host (may be empty for corpus-only)."
          },
          "source": {
            "type": "string",
            "description": "Breach-corpus dump label when host is unknown."
          }
        }
      },
      "MonitorLayerCounts": {
        "type": "object",
        "description": "Current and baseline exposure counts for one protection layer.",
        "properties": {
          "count": {
            "type": "integer"
          },
          "baseline": {
            "type": "integer",
            "description": "Set on the first check — alerts fire only on increases past this."
          }
        }
      },
      "MonitorDomainWatch": {
        "type": "object",
        "required": [
          "domain",
          "role",
          "layers"
        ],
        "description": "Daily exposure watch for a domain you own. company = DNS-verified email domain (employee layers); product = licensed app login host (users of your app).",
        "properties": {
          "domain": {
            "type": "string",
            "example": "acme.com"
          },
          "role": {
            "type": "string",
            "enum": [
              "company",
              "product"
            ]
          },
          "layers": {
            "type": "object",
            "properties": {
              "employeeOnSystem": {
                "$ref": "#/components/schemas/MonitorLayerCounts"
              },
              "employeeElsewhere": {
                "$ref": "#/components/schemas/MonitorLayerCounts"
              },
              "platformUser": {
                "$ref": "#/components/schemas/MonitorLayerCounts"
              },
              "corpus": {
                "$ref": "#/components/schemas/MonitorLayerCounts"
              }
            }
          },
          "breachLocations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BreachLocation"
            }
          },
          "lastCheckedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastAlertedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "MonitorListResponse": {
        "type": "object",
        "description": "Monitor settings, watched domains (three ownership layers), and enrolled people (emails masked). Reports where exposure appeared — never passwords.",
        "properties": {
          "monitorEnabled": {
            "type": "boolean",
            "description": "Whether daily monitor runs are enabled."
          },
          "monitorStealer": {
            "type": "boolean",
            "description": "Whether daily stolen-login sighting checks are enabled for enrolled people."
          },
          "monitorDomainExposure": {
            "type": "boolean",
            "description": "Whether owned-domain layer watching is enabled."
          },
          "lastRunAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the last monitor job finished."
          },
          "domains": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MonitorDomainWatch"
            }
          },
          "identities": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "emailMasked",
                "status",
                "enrolledAt"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "emailMasked": {
                  "type": "string",
                  "example": "j***@example.com",
                  "description": "PII-safe masked email."
                },
                "emailDomain": {
                  "type": "string",
                  "description": "Email domain portion only."
                },
                "userId": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Your user id when provided at enroll."
                },
                "audience": {
                  "type": "string",
                  "enum": [
                    "employee",
                    "customer"
                  ],
                  "description": "Who you enrolled: employee (staff) or customer (end user)."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "active",
                    "paused",
                    "removed"
                  ]
                },
                "enrolledAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "breachHits": {
                  "type": "integer",
                  "description": "Known breach hits for this identity."
                },
                "credentialLogHits": {
                  "type": "integer",
                  "description": "Stolen-login exposure count for this identity (opaque total)."
                },
                "breachLocations": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Where they were seen — service hosts / breach-source labels only. Never passwords.",
                  "example": [
                    "login.example.net",
                    "OldBreachDump"
                  ]
                },
                "firstSeenInLogsAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "First time this identity appeared in logs."
                },
                "lastSeenInLogsAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "Most recent log sighting."
                },
                "lastCheckedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "Last monitor check for this identity."
                },
                "lastAlertedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "Last time a monitor alert fired."
                }
              }
            }
          }
        }
      },
      "MonitorEnrollRequest": {
        "type": "object",
        "description": "Enroll specific people to watch. Say who each one is: employee (staff) or customer (end user of your app). No auto-enroll.",
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "example": "alex@acme.com"
          },
          "userId": {
            "type": "string",
            "maxLength": 256
          },
          "audience": {
            "type": "string",
            "enum": [
              "employee",
              "customer"
            ],
            "default": "customer",
            "description": "Applies to `email` and as the default for `emails[]` items.",
            "example": "employee"
          },
          "emails": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "email"
              ],
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "userId": {
                  "type": "string",
                  "maxLength": 256
                },
                "audience": {
                  "type": "string",
                  "enum": [
                    "employee",
                    "customer"
                  ]
                }
              }
            }
          }
        }
      },
      "EvaluateRequest": {
        "type": "object",
        "required": [
          "fingerprint"
        ],
        "description": "Browser SDK evaluate body. Prefer the hosted SDK over hand-rolling this. Unknown fields are ignored safely.",
        "properties": {
          "publicKey": {
            "type": "string",
            "description": "Publishable key if not sent via `x-api-key`."
          },
          "fingerprint": {
            "type": "string",
            "minLength": 8,
            "maxLength": 256,
            "description": "Visitor id from the browser SDK.",
            "example": "fp_demo_visitor_001"
          },
          "fingerprintBase": {
            "type": "string",
            "description": "Stable fingerprint base from the SDK."
          },
          "fingerprintConfidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "SDK confidence for the fingerprint (0–1)."
          },
          "turnstileToken": {
            "type": "string",
            "description": "Optional bot-challenge token when the evaluate policy requires one (licensed keys usually skip this)."
          },
          "userAgent": {
            "type": "string",
            "description": "Browser User-Agent."
          },
          "email": {
            "type": "string",
            "description": "Optional email when collected in-browser."
          },
          "userId": {
            "type": "string",
            "description": "Optional stable user id."
          },
          "behavioral": {
            "type": "object",
            "description": "SDK behavioral signals. Opaque object.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "probes": {
            "type": "object",
            "description": "SDK environment / automation probes. Opaque object.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "tlsFingerprint": {
            "$ref": "#/components/schemas/TlsFingerprint"
          },
          "tlsCapture": {
            "$ref": "#/components/schemas/TlsCapture"
          },
          "extensionSet": {
            "type": "object",
            "description": "Extension-set signals from the SDK.",
            "additionalProperties": {
              "type": "object"
            }
          },
          "attribution": {
            "type": "object",
            "description": "Landing / referrer / UTM attribution.",
            "additionalProperties": {
              "type": "object"
            }
          }
        }
      },
      "EvaluateResponse": {
        "type": "object",
        "required": [
          "verdictToken",
          "score",
          "verdict",
          "scores",
          "flags",
          "riskSignals",
          "tls",
          "network"
        ],
        "description": "Preliminary browser evaluation from the SDK. Always includes compact `tls`, `network`, facet `scores`, and boolean `flags`. On your server, call `POST /api/v1/check` with collect fields, `sessionId`, and/or `tlsCapture` (not the token alone).",
        "properties": {
          "verdictToken": {
            "type": "object",
            "required": [
              "token",
              "expiresAt"
            ],
            "description": "Short-lived token (≈5 min TTL) for your server check.",
            "properties": {
              "token": {
                "type": "string",
                "description": "Opaque token to forward server-side."
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time",
                "description": "Token expiry (ISO-8601)."
              }
            }
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Preliminary risk score (0–100)."
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "scores": {
            "$ref": "#/components/schemas/ScoreFacets"
          },
          "flags": {
            "$ref": "#/components/schemas/RiskFlags"
          },
          "risk": {
            "type": "object",
            "description": "Nested risk group (preferred).",
            "properties": {
              "signals": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RiskReason"
                }
              }
            }
          },
          "riskSignals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskReason"
            },
            "description": "Flat alias of `risk.signals`."
          },
          "detection": {
            "$ref": "#/components/schemas/DetectionPackView"
          },
          "tls": {
            "$ref": "#/components/schemas/TlsResponseSummary"
          },
          "network": {
            "$ref": "#/components/schemas/NetworkSummary"
          },
          "requestId": {
            "type": "string",
            "description": "Correlation id (also returned as `X-Request-Id` header)."
          }
        }
      }
    },
    "responses": {
      "Problem": {
        "description": "RFC 9457 Problem Details",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            },
            "description": "Request correlation id"
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait (on 429)"
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Unix timestamp (seconds) when the window resets"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/check": {
      "servers": [
        {
          "url": "https://abusegraph.com"
        }
      ],
      "post": {
        "tags": [
          "Risk Check"
        ],
        "operationId": "createRiskCheck",
        "summary": "Score a signup or login",
        "description": "Server-only risk check with your **secret** API key. Always returns nested `risk`, flat aliases, and a compact `tls` block (`captureStatus`, JA3/JA4 when known). Use `?expand=tls` for richer ClientHello fields. For browser SDK flows, create a `sessionId` or forward collect probes / `tlsCapture` into this check.",
        "security": [
          {
            "SecretApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "$ref": "#/components/parameters/SiteHeader"
          },
          {
            "$ref": "#/components/parameters/Expand"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckRequest"
              },
              "example": {
                "email": "user@example.com",
                "ip": "203.0.113.10",
                "event": "signup"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Risk decision",
            "headers": {
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResponse"
                },
                "example": {
                  "score": 72,
                  "verdict": "challenge",
                  "summary": "Recommend challenge: this signup showed throwaway or risky email.",
                  "event": "signup",
                  "risk": {
                    "signals": [
                      {
                        "type": "disposable_email",
                        "label": "Disposable email"
                      }
                    ],
                    "reasons": [
                      {
                        "code": "disposable_email",
                        "label": "Disposable email",
                        "category": "risky_email"
                      }
                    ],
                    "unusual": [],
                    "summary": "Recommend challenge: this signup showed throwaway or risky email."
                  },
                  "riskSignals": [
                    {
                      "type": "disposable_email",
                      "label": "Disposable email"
                    }
                  ],
                  "reasons": [
                    {
                      "code": "disposable_email",
                      "label": "Disposable email",
                      "category": "risky_email"
                    }
                  ],
                  "unusual": [],
                  "accountsLinked": 0,
                  "scores": {
                    "overall": 72,
                    "bot": 0,
                    "network": 0,
                    "email": 70,
                    "identity": 0,
                    "tls": 0,
                    "multipleAccounts": 0,
                    "riskSignals": 70
                  },
                  "flags": {
                    "vpn": false,
                    "tor": false,
                    "datacenter": false,
                    "proxy": false,
                    "abuseIp": false,
                    "disposableEmail": true,
                    "bot": false,
                    "multiAccount": false,
                    "velocity": false,
                    "breach": false
                  },
                  "network": {
                    "kind": "clean"
                  },
                  "email": {
                    "disposable": true,
                    "risky": true,
                    "breach": false
                  },
                  "tls": {
                    "present": false,
                    "captureStatus": "missing",
                    "flagged": false,
                    "reasons": []
                  },
                  "recommendation": {
                    "action": "challenge",
                    "title": "Challenge this signup",
                    "detail": "Score 72. Elevated risk signals fired.",
                    "bullets": [
                      "Disposable email"
                    ]
                  },
                  "detection": {
                    "version": 1,
                    "summary": "Email risk signals fired.",
                    "categories": [
                      {
                        "id": "risky_email",
                        "label": "Risky email",
                        "hit": true
                      }
                    ],
                    "compartments": [
                      {
                        "id": "email",
                        "label": "Email",
                        "severity": "high",
                        "hit": true,
                        "reasons": [
                          "disposable_email"
                        ],
                        "count": 1
                      }
                    ],
                    "topReasons": [
                      {
                        "type": "disposable_email",
                        "label": "Disposable email",
                        "category": "risky_email"
                      }
                    ],
                    "unusual": [],
                    "network": {
                      "kind": "clean"
                    },
                    "device": {
                      "automation": "none",
                      "confidence": "unknown"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "402": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/account": {
      "servers": [
        {
          "url": "https://abusegraph.com"
        }
      ],
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Workspace plan, usage, and domains",
        "description": "Pull account status with a **secret** API key. Returns plan, live-check usage, overage rates, licensed/verified domains, key metadata (prefix only), and rate limits. Never returns raw key material.",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Account snapshot",
            "headers": {
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/monitor": {
      "servers": [
        {
          "url": "https://abusegraph.com"
        }
      ],
      "get": {
        "tags": [
          "Monitor"
        ],
        "operationId": "listMonitoredIdentities",
        "summary": "List monitoring status, domains, and people",
        "description": "Returns monitor settings, per-domain layer counts (employees on your systems, employees elsewhere, users of your app) with breach locations, and enrolled people (emails masked). Never returns passwords or usernames.",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Monitor settings + domains + identities",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MonitorListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "tags": [
          "Monitor"
        ],
        "operationId": "enrollMonitoredIdentities",
        "summary": "Enroll people to watch",
        "description": "Watch specific employees or customers by email. Requires a **live** secret key (`sk_live_…`). Set `audience` to say who each person is.",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MonitorEnrollRequest"
              },
              "example": {
                "email": "alex@acme.com",
                "audience": "employee"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrollment results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "results"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request completed."
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MonitorEnrollResult"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "402": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "patch": {
        "tags": [
          "Monitor"
        ],
        "operationId": "updateMonitoredIdentity",
        "summary": "Update monitored identity status",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "status"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused",
                      "removed"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/email": {
      "servers": [
        {
          "url": "https://abusegraph.com"
        }
      ],
      "post": {
        "tags": [
          "Email Intelligence"
        ],
        "operationId": "evaluateEmailIntelligence",
        "summary": "Score an email (or batch)",
        "description": "Standalone email intelligence without a full risk check. Pass `email` or `emails` (max 1000). Single → `{ ok: true, result }`. Batch → `{ ok: true, results: [...] }`.",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "emails": {
                    "type": "array",
                    "maxItems": 1000,
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  }
                }
              },
              "example": {
                "email": "user@example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email intelligence",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "result"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "example": true
                        },
                        "result": {
                          "$ref": "#/components/schemas/EmailIntelResponse"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "ok",
                        "results"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "example": true
                        },
                        "results": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/EmailIntelResponse"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "ok": true,
                  "result": {
                    "email": "user@example.com",
                    "domain": "example.com",
                    "recommendation": "send",
                    "score": 12,
                    "disposable": false,
                    "risky": false,
                    "breach": false,
                    "signals": []
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/session": {
      "servers": [
        {
          "url": "https://abusegraph.com"
        }
      ],
      "post": {
        "tags": [
          "Session"
        ],
        "operationId": "createCheckSession",
        "summary": "Create a check session",
        "description": "Store collect payload under a `sessionId` (24h TTL). Send probes / TLS / attribution at the **top level** (not nested under `payload`). Later call `POST /api/v1/check` with `sessionId` + account fields.",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visitorId": {
                    "type": "string",
                    "maxLength": 512
                  },
                  "fingerprint": {
                    "type": "string",
                    "maxLength": 512
                  },
                  "fingerprintBase": {
                    "type": "string",
                    "maxLength": 512
                  },
                  "userAgent": {
                    "type": "string",
                    "maxLength": 1024
                  },
                  "probes": {
                    "type": "object",
                    "description": "SDK environment / automation probes.",
                    "additionalProperties": {
                      "type": "object"
                    }
                  },
                  "behavioral": {
                    "type": "object",
                    "description": "SDK behavioral signals.",
                    "additionalProperties": {
                      "type": "object"
                    }
                  },
                  "tlsFingerprint": {
                    "$ref": "#/components/schemas/TlsFingerprint"
                  },
                  "tlsCapture": {
                    "$ref": "#/components/schemas/TlsCapture"
                  },
                  "extensionSet": {
                    "type": "object",
                    "description": "Extension-set signals from the SDK.",
                    "additionalProperties": {
                      "type": "object"
                    }
                  },
                  "attribution": {
                    "type": "object",
                    "description": "Landing / referrer / UTM attribution.",
                    "additionalProperties": {
                      "type": "object"
                    }
                  }
                }
              },
              "example": {
                "fingerprint": "fp_demo_visitor_001",
                "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionCreateResponse"
                },
                "example": {
                  "sessionId": "sess_demo_example",
                  "expiresAt": "2026-07-20T22:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/linked-accounts": {
      "servers": [
        {
          "url": "https://abusegraph.com"
        }
      ],
      "get": {
        "tags": [
          "Linked Accounts"
        ],
        "operationId": "getLinkedAccounts",
        "summary": "List linked accounts",
        "description": "Walk the identity graph for neighbors of a visitor, email, or user id.",
        "security": [
          {
            "SecretApiKey": []
          },
          {
            "BearerSecretKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "name": "visitorId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Visitor / fingerprint id."
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "email"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Linked accounts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedAccountsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/v1/risk/evaluate": {
      "servers": [
        {
          "url": "https://api.abusegraph.com"
        }
      ],
      "post": {
        "tags": [
          "Browser Evaluate"
        ],
        "operationId": "evaluateBrowserRisk",
        "summary": "Preliminary browser evaluation",
        "description": "Called by the AbuseGraph browser SDK with a publishable key. Returns a short-lived verdict token + preliminary score. Includes realtime velocity checks when floods are detected. Do not call this with secret keys. Prefer the hosted SDK: `https://abusegraph.com/sdk/abusegraph.min.js`.",
        "security": [
          {
            "PublishableApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "$ref": "#/components/parameters/Expand"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EvaluateRequest"
              },
              "example": {
                "fingerprint": "fp_demo_visitor_001",
                "fingerprintConfidence": 0.92,
                "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preliminary verdict",
            "headers": {
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvaluateResponse"
                },
                "example": {
                  "verdictToken": {
                    "token": "vt_demo_example",
                    "expiresAt": "2026-07-19T22:05:00.000Z"
                  },
                  "score": 18,
                  "verdict": "allow",
                  "scores": {
                    "overall": 18,
                    "bot": 0,
                    "network": 0,
                    "email": 0,
                    "identity": 0,
                    "tls": 0,
                    "multipleAccounts": 0,
                    "riskSignals": 0
                  },
                  "flags": {
                    "vpn": false,
                    "tor": false,
                    "datacenter": false,
                    "proxy": false,
                    "abuseIp": false,
                    "disposableEmail": false,
                    "bot": false,
                    "multiAccount": false,
                    "velocity": false,
                    "breach": false
                  },
                  "riskSignals": [],
                  "tls": {
                    "present": true,
                    "captureStatus": "verified",
                    "flagged": false,
                    "reasons": [],
                    "ja4": "t13d1516h2_8daaf6152771"
                  },
                  "network": {
                    "kind": "clean"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "403": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          },
          "503": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/health": {
      "servers": [
        {
          "url": "https://api.abusegraph.com"
        }
      ],
      "get": {
        "tags": [
          "Browser Evaluate"
        ],
        "operationId": "edgeHealth",
        "summary": "Edge service health",
        "description": "Presence-only check that the API is ready. No secrets are returned.",
        "responses": {
          "200": {
            "description": "ok or degraded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "bindings": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "boolean"
                      },
                      "description": "Which optional platform services are available."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/config": {
      "servers": [
        {
          "url": "https://api.abusegraph.com"
        }
      ],
      "get": {
        "tags": [
          "Browser Evaluate"
        ],
        "operationId": "edgeConfig",
        "summary": "Public evaluate policy",
        "description": "Unauthenticated policy for the browser SDK — whether a bot challenge is required. Licensed publishable keys never hard-require a challenge.",
        "responses": {
          "200": {
            "description": "Policy snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "turnstileRequired",
                    "turnstileRequiredUnbound"
                  ],
                  "properties": {
                    "turnstileRequired": {
                      "type": "boolean",
                      "description": "Whether licensed evaluate requires a bot challenge (always false today)."
                    },
                    "turnstileRequiredUnbound": {
                      "type": "boolean",
                      "description": "Whether demo / unbound evaluate may require a bot challenge."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/visit/context": {
      "servers": [
        {
          "url": "https://api.abusegraph.com"
        }
      ],
      "get": {
        "tags": [
          "Browser Evaluate"
        ],
        "operationId": "visitContext",
        "summary": "Masked visitor network context",
        "description": "Public visitor network snapshot for demos. IP is masked (/24 or /48) — never a raw address.",
        "responses": {
          "200": {
            "description": "Masked visitor context",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ip": {
                      "type": "string",
                      "description": "Masked visitor IP (/24 or /48)."
                    },
                    "country": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "city": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "region": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "asn": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "asOrganization": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "userAgent": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}