diff --git a/packages/api/src/openapi/spec.json b/packages/api/src/openapi/spec.json new file mode 100644 index 0000000..babf73e --- /dev/null +++ b/packages/api/src/openapi/spec.json @@ -0,0 +1,3629 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "ShieldAI API Gateway", + "description": "ShieldAI API Gateway — Fastify-based API providing VoicePrint, SpamShield, DarkWatch, Home Title, Info Broker Removal, and more. Supports JWT and API-key authentication with tiered rate limiting.", + "version": "1.0.0", + "contact": { + "name": "FrenoCorp Engineering" + } + }, + "servers": [ + { + "url": "http://localhost:3000", + "description": "Local development" + }, + { + "url": "https://api.shieldai.com", + "description": "Production" + } + ], + "paths": { + "/": { + "get": { + "tags": ["System"], + "summary": "Root endpoint", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { "type": "string" } + } + } + } + } + } + } + } + }, + "/health": { + "get": { + "tags": ["System"], + "summary": "Health check", + "responses": { + "200": { + "description": "Service healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["ok"] }, + "timestamp": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + }, + "/api/v1/info": { + "get": { + "tags": ["System"], + "summary": "API version info", + "responses": { + "200": { + "description": "API version info", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": { "type": "string" }, + "environment": { "type": "string" }, + "build": { "type": "string" } + } + } + } + } + } + } + } + }, + "/api/v1/docs": { + "get": { + "tags": ["System"], + "summary": "API documentation (legacy JSON)", + "responses": { + "200": { + "description": "API documentation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "version": { "type": "string" }, + "endpoints": { + "type": "object", + "properties": { + "public": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { "type": "string" }, + "path": { "type": "string" }, + "description": { "type": "string" } + } + } + }, + "authenticated": { + "type": "array", + "items": { + "type": "object", + "properties": { + "method": { "type": "string" }, + "path": { "type": "string" }, + "description": { "type": "string" } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/auth/user/me": { + "get": { + "tags": ["Authentication"], + "summary": "Get current authenticated user", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "email": { "type": "string" }, + "role": { "type": "string" }, + "organizationId": { "type": "string", "nullable": true } + } + }, + "authType": { "type": "string", "enum": ["jwt", "api-key"] } + } + } + } + } + }, + "401": { + "description": "Authentication required", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + } + } + }, + "/api/v1/auth/services": { + "get": { + "tags": ["Authentication"], + "summary": "List available services", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Service list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "status": { "type": "string" } + } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/services/user": { + "get": { + "tags": ["Services"], + "summary": "User service proxy (placeholder)", + "responses": { + "200": { + "description": "User service info", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "service": { "type": "string" }, + "message": { "type": "string" }, + "timestamp": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + }, + "/api/v1/services/billing": { + "get": { + "tags": ["Services"], + "summary": "Billing service proxy (placeholder)", + "responses": { + "200": { + "description": "Billing service info", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "service": { "type": "string" }, + "message": { "type": "string" }, + "timestamp": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + }, + "/api/v1/services/notifications": { + "get": { + "tags": ["Services"], + "summary": "Notification service proxy (placeholder)", + "responses": { + "200": { + "description": "Notification service info", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "service": { "type": "string" }, + "message": { "type": "string" }, + "timestamp": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + }, + "/api/v1/devices/register": { + "post": { + "tags": ["Devices"], + "summary": "Register device for push notifications", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["platform", "fcmToken", "apnsToken", "appVersion", "osVersion"], + "properties": { + "platform": { "type": "string", "enum": ["ios", "android"] }, + "fcmToken": { "type": "string" }, + "apnsToken": { "type": "string" }, + "appVersion": { "type": "string" }, + "osVersion": { "type": "string" }, + "deviceModel": { "type": "string", "nullable": true } + } + } + } + } + }, + "responses": { + "200": { + "description": "Device registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "device": { + "type": "object", + "properties": { + "deviceId": { "type": "string" }, + "platform": { "type": "string" }, + "registeredAt": { "type": "string", "format": "date-time" } + } + }, + "message": { "type": "string" } + } + } + } + } + }, + "400": { + "description": "Missing required fields", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "401": { + "description": "Authentication required", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + } + } + }, + "/api/v1/devices/{deviceId}/tokens": { + "put": { + "tags": ["Devices"], + "summary": "Update device push tokens", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "deviceId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [], + "properties": { + "fcmToken": { "type": "string" }, + "apnsToken": { "type": "string" }, + "deviceModel": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Tokens updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "device": { + "type": "object", + "properties": { + "deviceId": { "type": "string" }, + "platform": { "type": "string" }, + "lastActiveAt": { "type": "string", "format": "date-time" } + } + }, + "message": { "type": "string" } + } + } + } + } + }, + "404": { + "description": "Device not found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + } + } + }, + "/api/v1/devices": { + "get": { + "tags": ["Devices"], + "summary": "Get user's registered devices", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Device list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "devices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "deviceId": { "type": "string" }, + "platform": { "type": "string" }, + "appVersion": { "type": "string" }, + "osVersion": { "type": "string" }, + "model": { "type": "string", "nullable": true }, + "lastActiveAt": { "type": "string", "format": "date-time" }, + "registeredAt": { "type": "string", "format": "date-time" } + } + } + }, + "message": { "type": "string" } + } + } + } + } + } + } + }, + "delete": { + "tags": ["Devices"], + "summary": "Deregister device", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "deviceId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Device deregistered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "message": { "type": "string" } + } + } + } + } + } + } + } + }, + "/api/v1/notifications/send": { + "post": { + "tags": ["Notifications"], + "summary": "Send a notification to a user", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["userId", "channel", "subject", "body"], + "properties": { + "userId": { "type": "string" }, + "channel": { "type": "string", "enum": ["email", "push", "sms"] }, + "subject": { "type": "string" }, + "body": { "type": "string" }, + "email": { "type": "string" }, + "phone": { "type": "string" }, + "fcmToken": { "type": "string" }, + "apnsToken": { "type": "string" }, + "priority": { "type": "string", "enum": ["low", "normal", "high", "urgent"] }, + "metadata": { "type": "object" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Notification sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "notifications": { "type": "array" } + } + } + } + } + }, + "503": { + "description": "Notification service not initialized" + } + } + } + }, + "/api/v1/notifications/{userId}/preferences": { + "get": { + "tags": ["Notifications"], + "summary": "Get notification preferences", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Preferences retrieved", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "preferences": { "type": "object" } + } + } + } + } + } + } + }, + "put": { + "tags": ["Notifications"], + "summary": "Update notification preferences", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "categories": { "type": "array", "items": { "type": "string" } } + } + }, + "push": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "categories": { "type": "array", "items": { "type": "string" } } + } + }, + "sms": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "categories": { "type": "array", "items": { "type": "string" } } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preferences updated" + } + } + } + }, + "/api/v1/notifications/config": { + "get": { + "tags": ["Notifications"], + "summary": "Get notification configuration status", + "responses": { + "200": { + "description": "Config retrieved", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "config": { "type": "object" } + } + } + } + } + } + } + } + }, + "/api/v1/subscription": { + "get": { + "tags": ["Subscription"], + "summary": "Get current user's subscription", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Subscription details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "subscription": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "status": { "type": "string" }, + "currentPeriodStart": { "type": "string", "format": "date-time" }, + "currentPeriodEnd": { "type": "string", "format": "date-time" }, + "cancelAtPeriodEnd": { "type": "boolean" }, + "created": { "type": "string", "format": "date-time" } + } + }, + "customer": { + "type": "object", + "properties": { + "id": { "type": "string" } + } + } + } + } + } + } + }, + "404": { + "description": "No active subscription found" + } + } + } + }, + "/api/v1/subscription/create": { + "post": { + "tags": ["Subscription"], + "summary": "Create a new subscription", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["tier", "customerId"], + "properties": { + "tier": { "type": "string", "enum": ["free", "basic", "plus", "premium"] }, + "customerId": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Subscription created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "subscription": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "status": { "type": "string" }, + "currentPeriodStart": { "type": "string", "format": "date-time" }, + "currentPeriodEnd": { "type": "string", "format": "date-time" } + } + }, + "customer": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "email": { "type": "string" } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/subscription/{subscriptionId}/tier": { + "put": { + "tags": ["Subscription"], + "summary": "Update subscription tier", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "subscriptionId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["tier"], + "properties": { + "tier": { "type": "string", "enum": ["free", "basic", "plus", "premium"] } + } + } + } + } + }, + "responses": { + "200": { + "description": "Subscription updated" + } + } + } + }, + "/api/v1/subscription/{subscriptionId}": { + "delete": { + "tags": ["Subscription"], + "summary": "Cancel subscription", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "subscriptionId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cancelAtPeriodEnd": { "type": "boolean" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Subscription cancelled" + } + } + } + }, + "/api/v1/customer/portal": { + "post": { + "tags": ["Subscription"], + "summary": "Create customer portal session", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["customerId", "returnUrl"], + "properties": { + "customerId": { "type": "string" }, + "returnUrl": { "type": "string", "format": "uri" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Portal session created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { "type": "string", "format": "uri" }, + "expiresAt": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + }, + "/api/v1/customer": { + "post": { + "tags": ["Subscription"], + "summary": "Create customer", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email"], + "properties": { + "email": { "type": "string", "format": "email" }, + "name": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Customer created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "customer": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "email": { "type": "string" }, + "name": { "type": "string" }, + "metadata": { "type": "object" } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/user/tier": { + "get": { + "tags": ["Subscription"], + "summary": "Get user tier", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "User tier", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tier": { "type": "string", "enum": ["free", "basic", "plus", "premium"] }, + "limits": { "type": "object" } + } + } + } + } + } + } + } + }, + "/api/v1/invoices": { + "get": { + "tags": ["Subscription"], + "summary": "Get invoice history", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "customerId", + "in": "query", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Invoice history", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "invoices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "amountDue": { "type": "number" }, + "amountPaid": { "type": "number" }, + "status": { "type": "string" }, + "created": { "type": "string", "format": "date-time" }, + "hostedInvoiceUrl": { "type": "string", "format": "uri" } + } + } + }, + "hasMore": { "type": "boolean" } + } + } + } + } + } + } + } + }, + "/api/v1/webhooks/stripe": { + "post": { + "tags": ["Subscription"], + "summary": "Stripe webhook handler (public)", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "type": "object" } + } + } + }, + "responses": { + "200": { + "description": "Webhook received" + }, + "400": { + "description": "Missing Stripe signature" + } + } + } + }, + "/api/v1/waitlist/signup": { + "post": { + "tags": ["Waitlist"], + "summary": "Sign up for waitlist", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email"], + "properties": { + "email": { "type": "string", "format": "email" }, + "name": { "type": "string" }, + "tier": { "type": "string", "enum": ["basic", "plus", "premium"] }, + "utmSource": { "type": "string" }, + "utmMedium": { "type": "string" }, + "utmCampaign": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Waitlist signup successful" + }, + "200": { + "description": "Already on waitlist" + }, + "400": { + "description": "Valid email is required" + } + } + } + }, + "/api/v1/waitlist/count": { + "get": { + "tags": ["Waitlist"], + "summary": "Get waitlist count", + "responses": { + "200": { + "description": "Waitlist count", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/v1/blog": { + "get": { + "tags": ["Blog"], + "summary": "List published blog posts", + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { "type": "string", "default": "1" } + }, + { + "name": "limit", + "in": "query", + "schema": { "type": "string", "default": "10" } + }, + { + "name": "tag", + "in": "query", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Blog posts", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "posts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "slug": { "type": "string" }, + "title": { "type": "string" }, + "excerpt": { "type": "string" }, + "authorName": { "type": "string" }, + "coverImageUrl": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } }, + "publishedAt": { "type": "string", "format": "date-time" }, + "viewCount": { "type": "integer" } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { "type": "integer" }, + "limit": { "type": "integer" }, + "total": { "type": "integer" }, + "totalPages": { "type": "integer" } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/blog/{slug}": { + "get": { + "tags": ["Blog"], + "summary": "Get blog post by slug", + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Blog post" + }, + "404": { + "description": "Post not found" + } + } + } + }, + "/api/v1/admin/blog": { + "get": { + "tags": ["Blog Admin"], + "summary": "List all blog posts (admin)", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { "type": "string", "default": "1" } + }, + { + "name": "limit", + "in": "query", + "schema": { "type": "string", "default": "20" } + } + ], + "responses": { + "200": { + "description": "Blog posts with pagination" + } + } + }, + "post": { + "tags": ["Blog Admin"], + "summary": "Create blog post", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["slug", "title", "content"], + "properties": { + "slug": { "type": "string" }, + "title": { "type": "string", "maxLength": 200 }, + "excerpt": { "type": "string" }, + "content": { "type": "string" }, + "authorName": { "type": "string" }, + "coverImageUrl": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } }, + "published": { "type": "boolean" }, + "publishedAt": { "type": "string", "format": "date-time" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Post created" + }, + "409": { + "description": "Slug already exists" + } + } + } + }, + "/api/v1/admin/blog/{id}": { + "put": { + "tags": ["Blog Admin"], + "summary": "Update blog post", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slug": { "type": "string" }, + "title": { "type": "string" }, + "excerpt": { "type": "string" }, + "content": { "type": "string" }, + "authorName": { "type": "string" }, + "coverImageUrl": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } }, + "published": { "type": "boolean" }, + "publishedAt": { "type": "string", "format": "date-time" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Post updated" + }, + "404": { + "description": "Post not found" + } + } + }, + "delete": { + "tags": ["Blog Admin"], + "summary": "Delete blog post", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "204": { + "description": "Post deleted" + } + } + } + }, + "/api/v1/extension/auth": { + "post": { + "tags": ["Extension"], + "summary": "Validate browser extension token", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "type": "object" } + } + } + }, + "responses": { + "200": { + "description": "Token validated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userId": { "type": "string" }, + "tier": { "type": "string" } + } + } + } + } + }, + "401": { + "description": "Bearer token required or token invalid" + } + } + } + }, + "/api/v1/extension/url-check": { + "post": { + "tags": ["Extension"], + "summary": "Check URL for phishing threats", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["url"], + "properties": { + "url": { "type": "string", "format": "uri" } + } + } + } + } + }, + "responses": { + "200": { + "description": "URL check result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { "type": "string" }, + "domain": { "type": "string" }, + "verdict": { "type": "string" }, + "confidence": { "type": "number" }, + "threats": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "severity": { "type": "string" }, + "source": { "type": "string" }, + "description": { "type": "string" } + } + } + }, + "timestamp": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/v1/extension/phishing-report": { + "post": { + "tags": ["Extension"], + "summary": "Report a phishing URL", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["url", "pageTitle", "tabId", "timestamp", "reason"], + "properties": { + "url": { "type": "string" }, + "pageTitle": { "type": "string" }, + "tabId": { "type": "integer" }, + "timestamp": { "type": "integer" }, + "reason": { "type": "string" }, + "heuristics": { "type": "object" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Phishing report submitted" + } + } + } + }, + "/api/v1/extension/stats": { + "get": { + "tags": ["Extension"], + "summary": "Get extension stats", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Extension stats", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "threatsBlockedToday": { "type": "integer" }, + "urlsCheckedToday": { "type": "integer" }, + "lastSyncAt": { "type": "string", "format": "date-time" }, + "syncDate": { "type": "string" } + } + } + } + } + } + } + } + }, + "/api/v1/extension/exposures/check": { + "post": { + "tags": ["Extension"], + "summary": "Check domain exposures", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["domain"], + "properties": { + "domain": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Exposure check result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "exposed": { "type": "boolean" }, + "sources": { "type": "array", "items": { "type": "string" } }, + "count": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/v1/voiceprint/enroll": { + "post": { + "tags": ["VoicePrint"], + "summary": "Enroll a new voice profile", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": ["audio"], + "properties": { + "audio": { "type": "string", "format": "binary" }, + "name": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Enrollment created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enrollment": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "isActive": { "type": "boolean" }, + "createdAt": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + }, + "400": { + "description": "Audio file is required" + }, + "422": { + "description": "Enrollment failed" + } + } + } + }, + "/api/v1/voiceprint/enrollments": { + "get": { + "tags": ["VoicePrint"], + "summary": "List user's voice enrollments", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "isActive", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "limit", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Enrollments list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enrollments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "isActive": { "type": "boolean" }, + "createdAt": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/voiceprint/enrollments/{id}": { + "delete": { + "tags": ["VoicePrint"], + "summary": "Remove a voice enrollment", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Enrollment removed" + }, + "404": { + "description": "Enrollment not found" + } + } + } + }, + "/api/v1/voiceprint/analyze": { + "post": { + "tags": ["VoicePrint"], + "summary": "Analyze a single audio file", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": ["audio"], + "properties": { + "audio": { "type": "string", "format": "binary" }, + "enrollmentId": { "type": "string" }, + "audioUrl": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Analysis result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "analysis": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "isSynthetic": { "type": "boolean" }, + "confidence": { "type": "number" }, + "analysisResult": { "type": "object" }, + "createdAt": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + }, + "400": { + "description": "Audio file is required" + } + } + } + }, + "/api/v1/voiceprint/results/{id}": { + "get": { + "tags": ["VoicePrint"], + "summary": "Get analysis result by ID", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Analysis result" + }, + "404": { + "description": "Analysis not found" + } + } + } + }, + "/api/v1/voiceprint/history": { + "get": { + "tags": ["VoicePrint"], + "summary": "Get analysis history", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "isSynthetic", + "in": "query", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Analysis history" + } + } + } + }, + "/api/v1/voiceprint/batch": { + "post": { + "tags": ["VoicePrint"], + "summary": "Batch analyze multiple audio files", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": ["audio"], + "properties": { + "audio": { "type": "string", "format": "binary" }, + "enrollmentId": { "type": "string" }, + "audioUrl": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Batch analysis job", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobId": { "type": "string" }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "isSynthetic": { "type": "boolean" }, + "confidence": { "type": "number" } + } + } + }, + "summary": { "type": "object" } + } + } + } + } + } + } + } + }, + "/api/v1/spamshield/sms/classify": { + "post": { + "tags": ["SpamShield"], + "summary": "Classify SMS text", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["text"], + "properties": { + "text": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "SMS classification", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "classification": { + "type": "object", + "properties": { + "isSpam": { "type": "boolean" }, + "confidence": { "type": "number" }, + "spamFeatures": { "type": "object" } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/spamshield/number/reputation": { + "post": { + "tags": ["SpamShield"], + "summary": "Check phone number reputation", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["phoneNumber"], + "properties": { + "phoneNumber": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Number reputation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reputation": { + "type": "object", + "properties": { + "isSpam": { "type": "boolean" }, + "confidence": { "type": "number" }, + "spamType": { "type": "string" }, + "reportCount": { "type": "integer" } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/spamshield/call/analyze": { + "post": { + "tags": ["SpamShield"], + "summary": "Analyze incoming call", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["phoneNumber", "callTime"], + "properties": { + "phoneNumber": { "type": "string" }, + "duration": { "type": "integer" }, + "callTime": { "type": "string", "format": "date-time" }, + "isVoip": { "type": "boolean" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Call analysis", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "analysis": { + "type": "object", + "properties": { + "decision": { "type": "string" }, + "confidence": { "type": "number" }, + "reasons": { "type": "array", "items": { "type": "string" } } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/spamshield/feedback": { + "post": { + "tags": ["SpamShield"], + "summary": "Record spam feedback", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["phoneNumber", "isSpam"], + "properties": { + "phoneNumber": { "type": "string" }, + "isSpam": { "type": "boolean" }, + "confidence": { "type": "number" }, + "metadata": { "type": "object" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Feedback recorded" + } + } + } + }, + "/api/v1/spamshield/history": { + "get": { + "tags": ["SpamShield"], + "summary": "Get spam history", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "isSpam", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "startDate", + "in": "query", + "schema": { "type": "string", "format": "date-time" } + } + ], + "responses": { + "200": { + "description": "Spam history" + } + } + } + }, + "/api/v1/spamshield/statistics": { + "get": { + "tags": ["SpamShield"], + "summary": "Get spam statistics", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Spam statistics" + } + } + } + }, + "/api/v1/darkwatch/watchlist": { + "get": { + "tags": ["DarkWatch"], + "summary": "List watchlist items", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Watchlist items" + }, + "404": { + "description": "Active subscription not found" + } + } + }, + "post": { + "tags": ["DarkWatch"], + "summary": "Add watchlist item", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["type", "value"], + "properties": { + "type": { "type": "string" }, + "value": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Watchlist item added" + }, + "400": { + "description": "Type and value are required" + } + } + } + }, + "/api/v1/darkwatch/watchlist/{id}": { + "delete": { + "tags": ["DarkWatch"], + "summary": "Remove watchlist item", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Watchlist item removed" + } + } + } + }, + "/api/v1/darkwatch/scan": { + "post": { + "tags": ["DarkWatch"], + "summary": "Trigger on-demand scan", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Scan queued", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "status": { "type": "string", "enum": ["queued"] } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/darkwatch/scan/schedule": { + "get": { + "tags": ["DarkWatch"], + "summary": "Get scan schedule", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Scan schedule" + } + } + } + }, + "/api/v1/darkwatch/exposures": { + "get": { + "tags": ["DarkWatch"], + "summary": "List exposures", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Exposures list" + } + } + } + }, + "/api/v1/darkwatch/alerts": { + "get": { + "tags": ["DarkWatch"], + "summary": "List alerts", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Alerts list" + } + } + } + }, + "/api/v1/darkwatch/alerts/{id}/read": { + "patch": { + "tags": ["DarkWatch"], + "summary": "Mark alert as read", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Alert marked as read" + } + } + } + }, + "/api/v1/darkwatch/webhook": { + "post": { + "tags": ["DarkWatch"], + "summary": "External webhook receiver", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["source", "identifier", "identifierType"], + "properties": { + "source": { "type": "string" }, + "identifier": { "type": "string" }, + "identifierType": { "type": "string" }, + "metadata": { "type": "object" }, + "timestamp": { "type": "string", "format": "date-time" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Webhook processed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "processed": { "type": "boolean" }, + "exposuresCreated": { "type": "integer" }, + "alertsCreated": { "type": "integer" } + } + } + } + } + }, + "400": { + "description": "Source, identifier, and identifierType are required" + }, + "401": { + "description": "Webhook signature and timestamp required or invalid" + } + } + } + }, + "/api/v1/darkwatch/scheduler/init": { + "post": { + "tags": ["DarkWatch"], + "summary": "Initialize scheduled scans", + "responses": { + "200": { + "description": "Scheduler initialized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "scheduled": { "type": "integer" }, + "jobs": { "type": "array" } + } + } + } + } + } + } + } + }, + "/api/v1/darkwatch/scheduler/reschedule": { + "post": { + "tags": ["DarkWatch"], + "summary": "Reschedule all scans", + "responses": { + "200": { + "description": "Reschedule complete" + } + } + } + }, + "/api/v1/reports": { + "get": { + "tags": ["Reports"], + "summary": "Get report history", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "string", "default": "20" } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "string", "default": "0" } + } + ], + "responses": { + "200": { + "description": "Report history" + } + } + } + }, + "/api/v1/reports/generate": { + "post": { + "tags": ["Reports"], + "summary": "Generate a new report", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reportType": { "type": "string" }, + "periodStart": { "type": "string", "format": "date-time" }, + "periodEnd": { "type": "string", "format": "date-time" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Report generated" + } + } + } + }, + "/api/v1/reports/schedule/monthly": { + "post": { + "tags": ["Reports"], + "summary": "Schedule monthly reports", + "responses": { + "200": { + "description": "Monthly reports scheduled" + } + } + } + }, + "/api/v1/reports/schedule/annual": { + "post": { + "tags": ["Reports"], + "summary": "Schedule annual reports", + "responses": { + "200": { + "description": "Annual reports scheduled" + } + } + } + }, + "/api/v1/reports/{reportId}": { + "get": { + "tags": ["Reports"], + "summary": "Get specific report", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "reportId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Report details" + }, + "404": { + "description": "Report not found" + } + } + } + }, + "/api/v1/reports/{reportId}/html": { + "get": { + "tags": ["Reports"], + "summary": "Get report HTML content", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "reportId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "HTML content", + "content": { + "text/html": { + "schema": { "type": "string" } + } + } + }, + "404": { + "description": "Report not found or not completed" + } + } + } + }, + "/api/v1/reports/{reportId}/pdf": { + "get": { + "tags": ["Reports"], + "summary": "Get report PDF", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "reportId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "PDF file", + "content": { + "application/pdf": { + "schema": { "type": "string", "format": "binary" } + } + } + } + } + } + }, + "/api/v1/hometitle/properties": { + "get": { + "tags": ["Home Title"], + "summary": "List monitored properties", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Properties list" + }, + "402": { + "description": "Premium tier required" + } + } + }, + "post": { + "tags": ["Home Title"], + "summary": "Add property to monitor", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["address"], + "properties": { + "address": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Property added" + }, + "400": { + "description": "Invalid address or property limit reached" + }, + "402": { + "description": "Premium tier required" + }, + "409": { + "description": "Duplicate property" + } + } + } + }, + "/api/v1/hometitle/properties/{id}": { + "delete": { + "tags": ["Home Title"], + "summary": "Remove property from monitoring", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Property removed" + }, + "404": { + "description": "Property not found" + } + } + } + }, + "/api/v1/hometitle/properties/stats": { + "get": { + "tags": ["Home Title"], + "summary": "Get dashboard widget stats", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Stats", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "monitoredProperties": { "type": "integer" }, + "tier": { "type": "string" }, + "isPremium": { "type": "boolean" }, + "propertyLimit": { "type": "integer" }, + "canAddMore": { "type": "boolean" }, + "recentAlertCount": { "type": "integer" }, + "criticalAlertCount": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/v1/hometitle/changes": { + "get": { + "tags": ["Home Title"], + "summary": "Get recent property changes", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 20, "maximum": 50 } + } + ], + "responses": { + "200": { + "description": "Property changes" + } + } + } + }, + "/api/v1/hometitle/alerts": { + "get": { + "tags": ["Home Title"], + "summary": "Get property alerts", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 20, "maximum": 50 } + }, + { + "name": "severity", + "in": "query", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Alerts list" + } + } + } + }, + "/api/v1/hometitle/alerts/{id}/read": { + "patch": { + "tags": ["Home Title"], + "summary": "Mark alert as read", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Alert marked as read" + } + } + } + }, + "/api/v1/hometitle/scan": { + "post": { + "tags": ["Home Title"], + "summary": "Trigger on-demand scan", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Scan initiated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "scan": { + "type": "object", + "properties": { + "scanId": { "type": "string" }, + "propertiesScanned": { "type": "integer" }, + "changesDetected": { "type": "integer" }, + "alertsCreated": { "type": "integer" }, + "notificationsSent": { "type": "integer" }, + "startedAt": { "type": "string", "format": "date-time" }, + "completedAt": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + } + } + }, + "/api/v1/scan": { + "post": { + "tags": ["Scan"], + "summary": "Run a scan", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "source": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scan completed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "scanned": { "type": "boolean" }, + "resultCount": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/v1/scan/history": { + "get": { + "tags": ["Scan"], + "summary": "Get scan history", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Scan history" + } + } + } + }, + "/api/v1/exposures": { + "get": { + "tags": ["Exposure"], + "summary": "Get user exposures", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Exposures list" + } + } + } + }, + "/api/v1/exposures/{id}": { + "get": { + "tags": ["Exposure"], + "summary": "Get exposure detail", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Exposure detail" + }, + "404": { + "description": "Exposure not found" + } + } + } + }, + "/api/v1/alerts": { + "get": { + "tags": ["Alert"], + "summary": "Get user alerts", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 50 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0 } + } + ], + "responses": { + "200": { + "description": "Alerts list" + } + } + } + }, + "/api/v1/alerts/{id}/read": { + "patch": { + "tags": ["Alert"], + "summary": "Mark alert as read", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Alert marked as read" + } + } + } + }, + "/api/v1/watchlist": { + "post": { + "tags": ["Watchlist"], + "summary": "Add watchlist item", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["identifierType", "identifierValue"], + "properties": { + "identifierType": { "type": "string" }, + "identifierValue": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Watchlist item added" + } + } + }, + "get": { + "tags": ["Watchlist"], + "summary": "List watchlist items", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Watchlist items" + } + } + } + }, + "/api/v1/watchlist/{id}": { + "delete": { + "tags": ["Watchlist"], + "summary": "Remove watchlist item", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Watchlist item removed" + } + } + } + }, + "/api/v1/webhook": { + "post": { + "tags": ["Webhook"], + "summary": "Process webhook event", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["eventType", "payload"], + "properties": { + "eventType": { "type": "string" }, + "payload": { "type": "object" }, + "source": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Event processed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "eventId": { "type": "string" }, + "scanTriggered": { "type": "boolean" } + } + } + } + } + }, + "400": { + "description": "Webhook processing failed" + } + } + } + }, + "/api/v1/webhook/history": { + "get": { + "tags": ["Webhook"], + "summary": "Get webhook event history", + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 50 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0 } + } + ], + "responses": { + "200": { + "description": "Event history" + } + } + } + }, + "/api/v1/webhook/user/{userId}": { + "get": { + "tags": ["Webhook"], + "summary": "Get user webhook events", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 50 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0 } + } + ], + "responses": { + "200": { + "description": "User webhook events" + } + } + } + }, + "/api/v1/scheduler/ensure": { + "post": { + "tags": ["Scheduler"], + "summary": "Ensure scan schedule for user", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "responses": { + "200": { + "description": "Schedule ensured" + } + } + } + }, + "/api/v1/scheduler/{userId}": { + "get": { + "tags": ["Scheduler"], + "summary": "Get scan schedule for user", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Scan schedule" + }, + "404": { + "description": "Schedule not found" + } + } + } + }, + "/api/v1/scheduler/{userId}/pause": { + "post": { + "tags": ["Scheduler"], + "summary": "Pause scan schedule", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Schedule paused" + } + } + } + }, + "/api/v1/scheduler/{userId}/resume": { + "post": { + "tags": ["Scheduler"], + "summary": "Resume scan schedule", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Schedule resumed" + } + } + } + }, + "/api/v1/scheduler": { + "get": { + "tags": ["Scheduler"], + "summary": "List active schedules", + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 100 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0 } + } + ], + "responses": { + "200": { + "description": "Active schedules" + } + } + } + }, + "/api/v1/dashboard": { + "get": { + "tags": ["Correlation"], + "summary": "Get dashboard data", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "timeWindow", + "in": "query", + "description": "Time window in minutes (1-10080), default 60", + "schema": { "type": "integer", "default": 60, "minimum": 1, "maximum": 10080 } + } + ], + "responses": { + "200": { + "description": "Dashboard data" + } + } + } + }, + "/api/v1/groups": { + "get": { + "tags": ["Correlation"], + "summary": "Get correlation groups", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "timeWindow", + "in": "query", + "schema": { "type": "integer", "default": 60, "minimum": 1, "maximum": 10080 } + }, + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0, "minimum": 0, "maximum": 10000 } + }, + { + "name": "status", + "in": "query", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Correlation groups" + } + } + } + }, + "/api/v1/groups/{groupId}": { + "get": { + "tags": ["Correlation"], + "summary": "Get correlation group detail", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "groupId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { + "description": "Correlation group detail" + }, + "404": { + "description": "Group not found" + } + } + }, + "patch": { + "tags": ["Correlation"], + "summary": "Resolve or re-open correlation group", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "groupId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "string", "enum": ["RESOLVED", "ACTIVE"] } + } + } + } + } + }, + "responses": { + "200": { + "description": "Group updated" + } + } + } + }, + "/api/v1/alerts/correlated": { + "get": { + "tags": ["Correlation"], + "summary": "Get correlated alerts", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "parameters": [ + { + "name": "timeWindow", + "in": "query", + "schema": { "type": "integer", "default": 60, "minimum": 1, "maximum": 10080 } + }, + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0, "minimum": 0, "maximum": 10000 } + }, + { + "name": "source", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "category", + "in": "query", + "schema": { "type": "string" } + }, + { + "name": "severity", + "in": "query", + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Correlated alerts" + } + } + } + }, + "/api/v1/ingest/darkwatch": { + "post": { + "tags": ["Correlation"], + "summary": "Ingest DarkWatch alert", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sourceAlertId", "breachName", "severity", "channel"], + "properties": { + "sourceAlertId": { "type": "string" }, + "exposureId": { "type": "string" }, + "breachName": { "type": "string", "maxLength": 500 }, + "severity": { "type": "string", "maxLength": 20 }, + "channel": { "type": "string", "maxLength": 50 }, + "dataType": { "type": "array", "items": { "type": "string" } }, + "dataSource": { "type": "string", "maxLength": 100 } + } + } + } + } + }, + "responses": { + "201": { + "description": "Alert ingested" + } + } + } + }, + "/api/v1/ingest/spamshield": { + "post": { + "tags": ["Correlation"], + "summary": "Ingest SpamShield alert", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sourceAlertId", "phoneNumber", "decision", "confidence"], + "properties": { + "sourceAlertId": { "type": "string" }, + "phoneNumber": { "type": "string", "maxLength": 20 }, + "decision": { "type": "string", "enum": ["BLOCK", "FLAG", "ALLOW"] }, + "confidence": { "type": "number", "minimum": 0, "maximum": 1 }, + "reasons": { "type": "array", "items": { "type": "string" } }, + "channel": { "type": "string", "enum": ["call", "sms"] }, + "hiyaReputationScore": { "type": "number" }, + "truecallerSpamScore": { "type": "number" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Alert ingested" + } + } + } + }, + "/api/v1/ingest/voiceprint": { + "post": { + "tags": ["Correlation"], + "summary": "Ingest VoicePrint alert", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sourceAlertId", "jobId", "verdict", "syntheticScore", "confidence"], + "properties": { + "sourceAlertId": { "type": "string" }, + "jobId": { "type": "string" }, + "verdict": { "type": "string", "enum": ["SYNTHETIC", "NATURAL", "UNCERTAIN"] }, + "syntheticScore": { "type": "number", "minimum": 0, "maximum": 1 }, + "confidence": { "type": "number", "minimum": 0, "maximum": 1 }, + "matchedEnrollmentId": { "type": "string" }, + "matchedSimilarity": { "type": "number" }, + "analysisType": { "type": "string", "maxLength": 50 } + } + } + } + } + }, + "responses": { + "201": { + "description": "Alert ingested" + } + } + } + }, + "/api/v1/ingest/call-analysis": { + "post": { + "tags": ["Correlation"], + "summary": "Ingest call analysis alert", + "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sourceAlertId", "callId"], + "properties": { + "sourceAlertId": { "type": "string" }, + "callId": { "type": "string" }, + "eventType": { "type": "string", "maxLength": 100 }, + "mosScore": { "type": "number", "minimum": 1, "maximum": 5 }, + "anomaly": { "type": "string", "maxLength": 500 }, + "sentiment": { + "type": "object", + "properties": { + "label": { "type": "string", "maxLength": 50 }, + "score": { "type": "number", "minimum": 0, "maximum": 1 } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Alert ingested" + } + } + } + }, + "/removebrokers/brokers": { + "get": { + "tags": ["Info Broker Removal"], + "summary": "List available data brokers", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "category", + "in": "query", + "schema": { "type": "string", "enum": ["PEOPLE_SEARCH", "BACKGROUND_CHECK", "PUBLIC_RECORDS", "REVERSE_LOOKUP", "SOCIAL_MEDIA"] }, + "description": "Filter by broker category" + } + ], + "responses": { + "200": { + "description": "List of available brokers", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "brokers": { + "type": "array", + "items": { "$ref": "#/components/schemas/BrokerInfo" } + } + } + } + } + } + }, + "401": { "description": "Unauthorized" } + } + } + }, + "/removebrokers/status": { + "get": { + "tags": ["Info Broker Removal"], + "summary": "Get removal request status for user", + "security": [{"bearerAuth": []}], + "responses": { + "200": { + "description": "Removal status overview", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "stats": { + "type": "object", + "properties": { + "total": { "type": "integer" }, + "pending": { "type": "integer" }, + "submitted": { "type": "integer" }, + "completed": { "type": "integer" }, + "failed": { "type": "integer" } + } + }, + "limit": { "type": "integer" }, + "remaining": { "type": "integer" }, + "tier": { "type": "string" }, + "requests": { + "type": "array", + "items": { "$ref": "#/components/schemas/RemovalRequestStatus" } + } + } + } + } + } + }, + "401": { "description": "Unauthorized" }, + "402": { "description": "Subscription required" } + } + } + }, + "/removebrokers/scan": { + "post": { + "tags": ["Info Broker Removal"], + "summary": "Scan for personal listings across brokers", + "security": [{"bearerAuth": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["fullName"], + "properties": { + "fullName": { "type": "string" }, + "email": { "type": "string" }, + "phone": { "type": "string" }, + "address": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scan results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "brokersScanned": { "type": "integer" }, + "listingsFound": { "type": "integer" }, + "results": { "type": "array", "items": { "$ref": "#/components/schemas/ScanResult" } } + } + } + } + } + }, + "400": { "description": "Invalid request" }, + "401": { "description": "Unauthorized" }, + "402": { "description": "Subscription required" } + } + } + }, + "/removebrokers/request": { + "post": { + "tags": ["Info Broker Removal"], + "summary": "Create a new removal request", + "security": [{"bearerAuth": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["brokerId", "fullName"], + "properties": { + "brokerId": { "type": "string" }, + "fullName": { "type": "string" }, + "email": { "type": "string" }, + "phone": { "type": "string" }, + "address": { + "type": "object", + "properties": { + "street": { "type": "string" }, + "city": { "type": "string" }, + "state": { "type": "string" }, + "zip": { "type": "string" } + } + }, + "dob": { "type": "string" }, + "notes": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Removal request created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request": { "$ref": "#/components/schemas/RemovalRequestOutput" } + } + } + } + } + }, + "400": { "description": "Invalid request or limit reached" }, + "401": { "description": "Unauthorized" }, + "402": { "description": "Subscription required" }, + "422": { "description": "Duplicate request" } + } + } + }, + "/removebrokers/request/{id}": { + "get": { + "tags": ["Info Broker Removal"], + "summary": "Get specific removal request", + "security": [{"bearerAuth": []}], + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } + ], + "responses": { + "200": { + "description": "Removal request details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request": { "$ref": "#/components/schemas/RemovalRequestDetail" } + } + } + } + } + }, + "401": { "description": "Unauthorized" }, + "402": { "description": "Subscription required" }, + "404": { "description": "Not found" } + } + }, + "delete": { + "tags": ["Info Broker Removal"], + "summary": "Cancel a removal request", + "security": [{"bearerAuth": []}], + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } + ], + "responses": { + "200": { + "description": "Request cancelled", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "status": { "type": "string", "enum": ["CANCELLED"] } + } + } + } + } + } + } + }, + "400": { "description": "Cannot cancel completed request" }, + "401": { "description": "Unauthorized" }, + "402": { "description": "Subscription required" }, + "404": { "description": "Not found" } + } + } + }, + "/removebrokers/process": { + "post": { + "tags": ["Info Broker Removal"], + "summary": "Trigger processing of pending removals (admin)", + "security": [{"bearerAuth": []}], + "responses": { + "200": { + "description": "Processing results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "processed": { "type": "integer" }, + "results": { "type": "array", "items": { "type": "object" } } + } + } + } + } + }, + "401": { "description": "Unauthorized" }, + "403": { "description": "Admin access required" } + } + } + }, + "/removebrokers/verify/{id}": { + "post": { + "tags": ["Info Broker Removal"], + "summary": "Manually verify a removal", + "security": [{"bearerAuth": []}], + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } + ], + "responses": { + "200": { + "description": "Verification result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "requestId": { "type": "string" }, + "completed": { "type": "boolean" }, + "stillListed": { "type": "boolean" } + } + } + } + } + }, + "401": { "description": "Unauthorized" }, + "402": { "description": "Subscription required" }, + "404": { "description": "Not found" } + } + } + } + }, + + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT token obtained from authentication. Set JWT_SECRET or NEXTAUTH_SECRET server-side." + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + "description": "API key for service-to-service authentication. Keys prefixed with 'premium_' get premium rate limits, 'plus_' get plus rate limits." + } + }, + "schemas": { + "ErrorResponse": { + "type": "object", + "required": ["error", "message", "statusCode", "timestamp", "path"], + "properties": { + "error": { + "type": "string", + "description": "Error name/type" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "statusCode": { + "type": "integer", + "description": "HTTP status code" + }, + "code": { + "type": "string", + "description": "Machine-readable error code" + }, + "details": { + "type": "object", + "description": "Additional error details" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp" + }, + "path": { + "type": "string", + "description": "Request path" + } + } + }, + "RateLimitResponse": { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": ["Too Many Requests"] + }, + "message": { + "type": "string" + }, + "tier": { + "type": "string", + "enum": ["basic", "plus", "premium"] + }, + "limit": { + "type": "integer" + }, + "reset": { + "type": "string", + "format": "date-time" + } + } + }, + "RateLimitHeaders": { + "type": "object", + "description": "Rate limit headers returned on every response (except /health)", + "properties": { + "X-RateLimit-Limit": { + "type": "integer", + "description": "Maximum requests per window" + }, + "X-RateLimit-Remaining": { + "type": "integer", + "description": "Remaining requests in current window" + }, + "X-RateLimit-Reset": { + "type": "integer", + "description": "Unix timestamp when the rate limit window resets" + }, + "Retry-After": { + "type": "integer", + "description": "Seconds until retry is possible (only on 429 responses)" + } + } + }, + "RateLimitTiers": { + "type": "object", + "description": "Rate limits by tier (requests per minute)", + "properties": { + "basic": { + "type": "object", + "properties": { + "windowMs": { "type": "integer", "example": 60000 }, + "maxRequests": { "type": "integer", "example": 100 } + } + }, + "plus": { + "type": "object", + "properties": { + "windowMs": { "type": "integer", "example": 60000 }, + "maxRequests": { "type": "integer", "example": 500 } + } + }, + "premium": { + "type": "object", + "properties": { + "windowMs": { "type": "integer", "example": 60000 }, + "maxRequests": { "type": "integer", "example": 2000 } + } + } + } + }, + "SubscriptionTiers": { + "type": "object", + "description": "Subscription tiers and their feature limits", + "properties": { + "free": { + "type": "object", + "properties": { + "maxWatchlistItems": { "type": "integer" }, + "maxProperties": { "type": "integer" } + } + }, + "basic": { + "type": "object", + "properties": { + "maxWatchlistItems": { "type": "integer" }, + "maxProperties": { "type": "integer" } + } + }, + "plus": { + "type": "object", + "properties": { + "maxWatchlistItems": { "type": "integer" }, + "maxProperties": { "type": "integer" } + } + }, + "premium": { + "type": "object", + "properties": { + "maxWatchlistItems": { "type": "integer" }, + "maxProperties": { "type": "integer" } + } + } + } + }, + "BrokerInfo": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "domain": { "type": "string" }, + "category": { "type": "string", "enum": ["PEOPLE_SEARCH", "BACKGROUND_CHECK", "PUBLIC_RECORDS", "REVERSE_LOOKUP", "SOCIAL_MEDIA"] }, + "removalMethod": { "type": "string", "enum": ["AUTOMATED", "MANUAL_FORM", "EMAIL", "PHONE", "MAIL", "NONE"] }, + "requiresAccount": { "type": "boolean" }, + "requiresVerification": { "type": "boolean" }, + "estimatedDays": { "type": "integer" }, + "removalUrl": { "type": "string", "nullable": true } + } + }, + "RemovalRequestStatus": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "brokerId": { "type": "string" }, + "brokerName": { "type": "string" }, + "status": { "type": "string", "enum": ["PENDING", "SUBMITTED", "IN_PROGRESS", "COMPLETED", "FAILED", "REJECTED", "CANCELLED"] }, + "method": { "type": "string" }, + "attempts": { "type": "integer" }, + "submittedAt": { "type": "string", "format": "date-time", "nullable": true }, + "completedAt": { "type": "string", "format": "date-time", "nullable": true }, + "error": { "type": "string", "nullable": true }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "RemovalRequestOutput": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "brokerId": { "type": "string" }, + "status": { "type": "string" }, + "method": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" } + } + }, + "RemovalRequestDetail": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "brokerId": { "type": "string" }, + "brokerName": { "type": "string", "nullable": true }, + "status": { "type": "string", "enum": ["PENDING", "SUBMITTED", "IN_PROGRESS", "COMPLETED", "FAILED", "REJECTED", "CANCELLED"] }, + "method": { "type": "string" }, + "attempts": { "type": "integer" }, + "submittedAt": { "type": "string", "format": "date-time", "nullable": true }, + "completedAt": { "type": "string", "format": "date-time", "nullable": true }, + "error": { "type": "string", "nullable": true }, + "notes": { "type": "string", "nullable": true }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "ScanResult": { + "type": "object", + "properties": { + "brokerId": { "type": "string" }, + "brokerName": { "type": "string" }, + "found": { "type": "boolean" }, + "listingId": { "type": "string", "nullable": true }, + "url": { "type": "string", "nullable": true } + } + } + } + }, + + "security": [ + { "bearerAuth": [] }, + { "apiKeyAuth": [] } + ] +} diff --git a/packages/api/src/routes/removebrokers.routes.ts b/packages/api/src/routes/removebrokers.routes.ts index 5f8403c..6f7d519 100644 --- a/packages/api/src/routes/removebrokers.routes.ts +++ b/packages/api/src/routes/removebrokers.routes.ts @@ -270,7 +270,7 @@ export async function removebrokersRoutes(fastify: FastifyInstance) { request: { id: req.id, brokerId: req.brokerId, - brokerName: req.broker.name, + brokerName: req.broker?.name || null, status: req.status, method: req.method, attempts: req.attempts, @@ -313,13 +313,13 @@ export async function removebrokersRoutes(fastify: FastifyInstance) { await prisma.removalRequest.update({ where: { id }, - data: { status: RemovalStatus.REJECTED }, + data: { status: RemovalStatus.CANCELLED }, }); return reply.send({ request: { id: req.id, - status: RemovalStatus.REJECTED, + status: RemovalStatus.CANCELLED, }, }); } catch (error) { @@ -335,7 +335,7 @@ export async function removebrokersRoutes(fastify: FastifyInstance) { return reply.code(401).send({ error: 'User not authenticated' }); } - if (authReq.user.role !== 'admin' && authReq.user.role !== 'support') { + if (authReq.user.role !== 'support') { return reply.code(403).send({ error: 'Admin access required' }); } diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index df0ba4b..d108f75 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -838,6 +838,7 @@ enum RemovalStatus { COMPLETED FAILED REJECTED + CANCELLED } model InfoBroker { diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b3f7d27..d8d910e 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -399,6 +399,7 @@ export const RemovalStatus = { COMPLETED: "COMPLETED", FAILED: "FAILED", REJECTED: "REJECTED", + CANCELLED: "CANCELLED", } as const; export type RemovalStatus = (typeof RemovalStatus)[keyof typeof RemovalStatus]; @@ -416,7 +417,7 @@ export interface BrokerDefinition { id: string; name: string; domain: string; - category: string; + category: BrokerCategory; removalMethod: RemovalMethod; removalUrl?: string; requiresAccount: boolean; diff --git a/services/removebrokers/src/BrokerAlertPipeline.ts b/services/removebrokers/src/BrokerAlertPipeline.ts index a25124f..25629fd 100644 --- a/services/removebrokers/src/BrokerAlertPipeline.ts +++ b/services/removebrokers/src/BrokerAlertPipeline.ts @@ -1,4 +1,4 @@ -import { AlertSource, AlertCategory, Severity, EntityType } from "@shieldai/types"; +import { AlertSource, AlertCategory, Severity, EntityType, NormalizedAlertInput } from "@shieldai/types"; export interface BrokerAlertInput { userId: string; @@ -55,7 +55,7 @@ export class BrokerAlertPipeline { return this.normalizeAndSend(alert); } - private async normalizeAndSend(alert: any) { + private async normalizeAndSend(alert: NormalizedAlertInput) { try { const { correlationPipeline } = await import("@shieldai/correlation"); return correlationPipeline.normalizeAlert(alert); diff --git a/services/removebrokers/src/RemoveBrokersService.ts b/services/removebrokers/src/RemoveBrokersService.ts index aceb973..ba39620 100644 --- a/services/removebrokers/src/RemoveBrokersService.ts +++ b/services/removebrokers/src/RemoveBrokersService.ts @@ -3,6 +3,34 @@ import { RemovalStatus, RemovalMethod } from "@shieldai/types"; import { getBrokerById, getActiveBrokers } from "./brokerRegistry"; import type { PersonalInfo, RemovalJob, BrokerEntry } from "./types"; import { MAX_REMOVAL_ATTEMPTS, RETRY_DELAY_MS } from "./types"; +import type { RemovalRequest as PrismaRemovalRequest, InfoBroker } from "@shieldai/db"; + +function toPersonalInfo(raw: unknown): PersonalInfo | null { + if (typeof raw !== "object" || raw === null) return null; + const obj = raw as Record; + if (typeof obj.fullName !== "string") return null; + let address: PersonalInfo["address"] = undefined; + if (typeof obj.address === "object" && obj.address !== null) { + const addr = obj.address as Record; + address = { + street: typeof addr.street === "string" ? addr.street : undefined, + city: typeof addr.city === "string" ? addr.city : undefined, + state: typeof addr.state === "string" ? addr.state : undefined, + zip: typeof addr.zip === "string" ? addr.zip : undefined, + }; + } + return { + fullName: obj.fullName, + email: typeof obj.email === "string" ? obj.email : undefined, + phone: typeof obj.phone === "string" ? obj.phone : undefined, + address, + dob: typeof obj.dob === "string" ? obj.dob : undefined, + }; +} + +type RemovalRequestWithBroker = PrismaRemovalRequest & { + broker: InfoBroker; +}; export class RemoveBrokersService { async scanForListings(subscriptionId: string, personalInfo: PersonalInfo) { @@ -88,7 +116,7 @@ export class RemoveBrokersService { subscriptionId, brokerId, status: RemovalStatus.PENDING, - personalInfo: personalInfo as any, + personalInfo: JSON.parse(JSON.stringify(personalInfo)), method: broker.removalMethod, notes, }, @@ -139,7 +167,7 @@ export class RemoveBrokersService { requestId: request.id, brokerId: request.brokerId, brokerName: getBrokerById(request.brokerId)?.name || request.brokerId, - personalInfo: request.personalInfo as PersonalInfo, + personalInfo: toPersonalInfo(request.personalInfo)!, method: request.method, attempt: request.attempts + 1, }; @@ -203,7 +231,10 @@ export class RemoveBrokersService { throw new Error(`Removal request not found: ${requestId}`); } - const personalInfo = request.personalInfo as PersonalInfo; + const personalInfo = toPersonalInfo(request.personalInfo); + if (!personalInfo) { + throw new Error(`Invalid personal info in request ${requestId}`); + } const stillListed = await this.checkBrokerListing( request.broker, personalInfo, @@ -242,7 +273,7 @@ export class RemoveBrokersService { orderBy: { updatedAt: "desc" }, }); - return requests.map((r: any) => ({ + return requests.map((r: RemovalRequestWithBroker) => ({ id: r.id, brokerId: r.brokerId, brokerName: r.broker.name,