{
  "openapi": "3.1.0",
  "info": {
    "title": "Fraud Filter API",
    "version": "2.1.0",
    "description": "Detect scams across text, images, invoices, domains, logins, and multi-channel conversations. Authenticate with an API key in the X-API-Key header.",
    "contact": { "name": "Fraud Filter", "url": "https://fraudfilter.space/docs" }
  },
  "servers": [{ "url": "https://fraudfilter.space" }],
  "security": [{ "ApiKeyAuth": [] }],
  "paths": {
    "/api/check-job": {
      "post": {
        "summary": "Scan content",
        "description": "Analyze a single piece of content (text and/or a screenshot) for scams across seven categories.",
        "operationId": "checkJob",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScanRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScanResult" }
              }
            }
          },
          "401": { "description": "Missing or invalid API key" },
          "429": { "description": "Monthly quota exceeded" }
        }
      }
    },
    "/api/reverse-image": {
      "post": {
        "summary": "Reverse-image lookup",
        "description": "Detect a stolen or stock/model photo — an image reused across the web. A catfish / fake-listing tell. Requires the reverse-image feature to be configured on the backend.",
        "operationId": "reverseImage",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ImageCheckRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reverse-image result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReverseImageResult" }
              }
            }
          },
          "401": { "description": "Missing or invalid API key" },
          "429": { "description": "Monthly quota exceeded" },
          "503": { "description": "Feature not configured" }
        }
      }
    },
    "/api/check-deepfake": {
      "post": {
        "summary": "Deepfake / AI-image check",
        "description": "Detect a deepfake / face-swap or AI-generated image — a fake-profile tell that catches synthetic photos appearing nowhere else online. Requires the deepfake feature to be configured on the backend.",
        "operationId": "checkDeepfake",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ImageCheckRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deepfake result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DeepfakeResult" }
              }
            }
          },
          "401": { "description": "Missing or invalid API key" },
          "429": { "description": "Monthly quota exceeded" },
          "503": { "description": "Feature not configured" }
        }
      }
    },
    "/api/check-invoice": {
      "post": {
        "summary": "Invoice / BEC screening",
        "description": "Extract an invoice's payment fields and optionally diff them against a prior invoice from the same vendor — catches invoice-redirect (BEC/VEC) fraud. invoice_data is a base64 image or PDF page.",
        "operationId": "checkInvoice",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["invoice_data"],
                "properties": {
                  "invoice_data": { "type": "string", "description": "Base64 image/PDF-page of the new invoice." },
                  "invoice_mime": { "type": "string" },
                  "prior_data": { "type": "string", "description": "Optional prior invoice to diff against." },
                  "prior_mime": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Invoice assessment (risk_score, verdict, fields, changes, vendor_flags)" },
          "401": { "description": "Missing or invalid API key" },
          "429": { "description": "Monthly quota exceeded" }
        }
      }
    },
    "/api/check-brand-spoof": {
      "post": {
        "summary": "Brand-spoof / clone check",
        "description": "Is a suspect page a lookalike / clone of your official URL? Domain forensics plus optional content similarity. Supply the suspect as a URL, pasted text, or a screenshot.",
        "operationId": "checkBrandSpoof",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["official_url"],
                "properties": {
                  "official_url": { "type": "string", "description": "The brand's real page (the thing being cloned)." },
                  "suspect_url": { "type": "string" },
                  "suspect_text": { "type": "string", "description": "Pasted HTML/text of the suspect, if it can't be fetched." },
                  "suspect_image_data": { "type": "string", "description": "Base64 screenshot of the suspect." },
                  "suspect_image_mime": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Spoof assessment (risk_score, verdict, lookalike_domain, domain_signals, clone_likelihood)" },
          "401": { "description": "Missing or invalid API key" }
        }
      }
    },
    "/api/check-login-event": {
      "post": {
        "summary": "Account-takeover scoring",
        "description": "Score one of your users' login / security events for account takeover — new device + new geography + a sensitive action is the ATO fingerprint. Requires a Business workspace key.",
        "operationId": "checkLoginEvent",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["user_id"],
                "properties": {
                  "user_id": { "type": "string", "description": "Your OWN end-user id (opaque to us)." },
                  "device_fingerprint": { "type": "string", "description": "Stable per-device id from the client." },
                  "ip": { "type": "string" },
                  "country": { "type": "string", "description": "ISO code or name; used for geo-jump detection." },
                  "action": { "type": "string", "description": "e.g. login | password_change | payment_update | profile_edit." },
                  "user_agent": { "type": "string" },
                  "at": { "type": "string", "description": "ISO timestamp of the event (defaults to now)." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "ATO risk assessment" },
          "401": { "description": "Missing or invalid API key" }
        }
      }
    },
    "/api/thread-scan": {
      "post": {
        "summary": "Conversation thread scan",
        "description": "Score a whole conversation (not per-message) for romance / grooming trajectory — love-bombing to trust to isolation to financial ask.",
        "operationId": "threadScan",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["messages"],
                "properties": {
                  "messages": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["direction", "text"],
                      "properties": {
                        "direction": { "type": "string", "enum": ["them", "me"] },
                        "text": { "type": "string" },
                        "at": { "type": "string", "description": "ISO timestamp (optional)." }
                      }
                    }
                  },
                  "contact": { "type": "string", "description": "Who the messages are from (name / handle / number)." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Thread assessment (scam_type, stage, escalation, patterns, financial_asks)" },
          "401": { "description": "Missing or invalid API key" }
        }
      }
    },
    "/api/check-fraud-thread": {
      "post": {
        "summary": "Multi-channel fraud thread",
        "description": "Correlate communications across channels (email, phone, SMS, chat, letter) that together form one coordinated fraud attempt. Needs at least two events.",
        "operationId": "checkFraudThread",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["events"],
                "properties": {
                  "events": {
                    "type": "array",
                    "minItems": 2,
                    "items": {
                      "type": "object",
                      "required": ["channel", "content"],
                      "properties": {
                        "channel": { "type": "string", "enum": ["email", "phone", "sms", "chat", "letter", "other"] },
                        "content": { "type": "string" },
                        "sender": { "type": "string", "description": "Who it's from (email / number / name / handle)." },
                        "at": { "type": "string", "description": "ISO timestamp (optional)." }
                      }
                    }
                  },
                  "claimed_identity": { "type": "string", "description": "Who the events collectively claim to be from." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Coordinated-fraud assessment (coordinated, channels, coordination_signals)" },
          "401": { "description": "Missing or invalid API key" }
        }
      }
    },
    "/api/batch-scan": {
      "post": {
        "summary": "Batch scan",
        "description": "Score up to 50 items in one request.",
        "operationId": "batchScan",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["items"],
                "properties": {
                  "items": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "properties": {
                        "content": { "type": "string" },
                        "contact_email": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Array of scan results" },
          "401": { "description": "Missing or invalid API key" }
        }
      }
    },
    "/api/verify-domain": {
      "post": {
        "summary": "Verify a domain",
        "description": "Reputation + email-authentication heuristics (MX / SPF / DKIM / DMARC) for a single domain or URL.",
        "operationId": "verifyDomain",
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domain"],
                "properties": { "domain": { "type": "string" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Domain assessment" },
          "401": { "description": "Missing or invalid API key" }
        }
      }
    },
    "/api/scan-demo": {
      "post": {
        "summary": "Keyless demo scan",
        "description": "Public, rate-limited scan endpoint — no API key. Intended for trials and the consumer scanner.",
        "operationId": "scanDemo",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScanRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScanResult" }
              }
            }
          },
          "429": { "description": "Rate limit reached" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" }
    },
    "schemas": {
      "ScanRequest": {
        "type": "object",
        "description": "Provide content, a title, or an image.",
        "properties": {
          "content": { "type": "string", "description": "The full text to analyze." },
          "contact_email": { "type": "string", "description": "Sender address; matched against your blocklist." },
          "image_data": { "type": "string", "description": "Base64 screenshot (raw or data: URL). Read via built-in OCR. Max ~5MB." },
          "image_mime": { "type": "string", "description": "e.g. image/png, image/jpeg." },
          "title": { "type": "string" },
          "company": { "type": "string" },
          "salary": { "type": "string" },
          "url": { "type": "string", "description": "(demo only) fetch + scan a page." }
        }
      },
      "ScanResult": {
        "type": "object",
        "properties": {
          "overall_risk_score": { "type": "integer", "minimum": 0, "maximum": 100 },
          "overall_verdict": { "type": "string", "enum": ["low", "medium", "high", "critical"] },
          "scam_categories": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "score": { "type": "integer" },
                "flags": { "type": "array", "items": { "type": "string" } },
                "details": { "type": "string" }
              }
            }
          },
          "recommendations": { "type": "array", "items": { "type": "string" } },
          "explanation": {
            "type": "object",
            "properties": {
              "summary": { "type": "string" },
              "confidence": { "type": "integer" }
            }
          },
          "reverse_image": {
            "description": "Present only when an image was attached and matched the reverse-image check.",
            "$ref": "#/components/schemas/ImageSignal"
          },
          "deepfake": {
            "description": "Present only when an image was attached and matched the deepfake check.",
            "$ref": "#/components/schemas/ImageSignal"
          },
          "blacklisted": {
            "type": ["object", "null"],
            "properties": {
              "value": { "type": "string" },
              "reason": { "type": "string" }
            }
          },
          "calls_remaining": { "type": "integer", "description": "Authenticated endpoints only." }
        }
      },
      "ImageCheckRequest": {
        "type": "object",
        "description": "Provide the image as base64 (image_data) or a fetchable URL (image_url).",
        "properties": {
          "image_data": { "type": "string", "description": "Base64 image (raw or data: URL)." },
          "image_mime": { "type": "string", "description": "e.g. image/png, image/jpeg." },
          "image_url": { "type": "string", "description": "A public URL the service can fetch." }
        }
      },
      "ImageSignal": {
        "type": ["object", "null"],
        "description": "A matched image signal, or null when nothing was found.",
        "properties": {
          "signal": { "type": "string", "description": "e.g. stock_or_model_image, image_reused_across_web, deepfake_face, ai_generated_image." },
          "severity": { "type": "string", "enum": ["review", "high"] },
          "reason": { "type": "string" }
        }
      },
      "ReverseImageResult": {
        "type": "object",
        "properties": {
          "status": { "type": "string" },
          "matched": { "type": "boolean" },
          "signal": { "$ref": "#/components/schemas/ImageSignal" },
          "best_guess": { "type": ["string", "null"], "description": "Google's best-guess label for the image." },
          "calls_remaining": { "type": ["integer", "null"] }
        }
      },
      "DeepfakeResult": {
        "type": "object",
        "properties": {
          "status": { "type": "string" },
          "matched": { "type": "boolean" },
          "signal": { "$ref": "#/components/schemas/ImageSignal" },
          "scores": {
            "type": "object",
            "properties": {
              "deepfake": { "type": ["number", "null"], "description": "Probability 0..1 the face is a deepfake." },
              "ai_generated": { "type": ["number", "null"], "description": "Probability 0..1 the image is AI-generated." }
            }
          },
          "calls_remaining": { "type": ["integer", "null"] }
        }
      }
    }
  }
}
