{"openapi":"3.0.3","info":{"title":"ClickLens API","version":"1.0.0","description":"Click fraud detection and ad traffic quality API"},"servers":[{"url":"/api/v1","description":"API v1"}],"paths":{"/beacon":{"post":{"summary":"Ingest session signals","description":"Primary data ingestion endpoint used by the ClickLens tracking tag. Accepts session signals and behavioural data, scores the session, and stores the result. Always returns 204 — errors are never revealed to the client.","tags":["Public"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteKey","sessionKey","signals","behaviour"],"properties":{"siteKey":{"type":"string","description":"Site API key"},"sessionKey":{"type":"string","description":"Unique session identifier"},"signals":{"type":"object","description":"Browser/device signals (SessionSignals)","properties":{"userAgent":{"type":"string"},"platform":{"type":"string"},"language":{"type":"string"},"screenWidth":{"type":"number"},"screenHeight":{"type":"number"},"webdriver":{"type":"boolean"},"timezone":{"type":"string"},"referrer":{"type":"string"},"url":{"type":"string"}}},"behaviour":{"type":"object","description":"User behaviour data (BehaviourData)","properties":{"mouseMovements":{"type":"array"},"mouseClicks":{"type":"array"},"scrollEvents":{"type":"array"},"maxScrollDepth":{"type":"number"},"keydownCount":{"type":"number"},"timeOnPage":{"type":"number"},"pageVisible":{"type":"boolean"},"mouseEntropy":{"type":"number"}}},"fingerprintHash":{"type":"string","description":"Client-generated fingerprint hash"}}}}}},"responses":{"204":{"description":"Always returned (success or failure)"}}},"options":{"summary":"CORS preflight","tags":["Public"],"security":[],"responses":{"204":{"description":"CORS headers returned"}}}},"/audit":{"post":{"summary":"Run a site security audit","description":"Public endpoint that scans a URL for security headers, suspicious HTML patterns, and DNS configuration. Rate limited to 5 requests per IP per hour.","tags":["Public"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","example":"example.com"}}}}}},"responses":{"200":{"description":"Audit results","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"riskLevel":{"type":"string","enum":["low","medium","high"]},"fetchSucceeded":{"type":"boolean"},"securityHeaders":{"type":"object","properties":{"csp":{"type":"boolean"},"xFrameOptions":{"type":"boolean"},"xContentTypeOptions":{"type":"boolean"},"strictTransportSecurity":{"type":"boolean"},"referrerPolicy":{"type":"boolean"}}},"findings":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"severity":{"type":"string","enum":["low","medium","high"]},"description":{"type":"string"}}}},"findingCounts":{"type":"object","properties":{"high":{"type":"number"},"medium":{"type":"number"},"low":{"type":"number"}}},"scannedAt":{"type":"string","format":"date-time"}}}}}},"429":{"description":"Rate limit exceeded"}}}},"/dashboard":{"get":{"summary":"Get dashboard KPIs","description":"Returns aggregated dashboard data including session counts, verdict breakdown, estimated waste, traffic-over-time chart data, source breakdown, and top threats.","tags":["Dashboard"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"siteId","in":"query","required":true,"schema":{"type":"string"}},{"name":"startDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Dashboard data","content":{"application/json":{"schema":{"type":"object","properties":{"totalSessions":{"type":"number"},"humanPercent":{"type":"number"},"suspectPercent":{"type":"number"},"botPercent":{"type":"number"},"estimatedWaste":{"type":"number"},"trafficOverTime":{"type":"array"},"sourceBreakdown":{"type":"array"},"topThreats":{"type":"array"}}}}}},"401":{"description":"Not authenticated"},"403":{"description":"No access to site"}}}},"/sessions":{"get":{"summary":"List sessions","description":"Returns paginated session list with filtering by verdict, date range, source, and search term.","tags":["Sessions"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"siteId","in":"query","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"number","default":1}},{"name":"limit","in":"query","schema":{"type":"number","default":50}},{"name":"verdict","in":"query","schema":{"type":"string","enum":["human","suspect","bot"]}},{"name":"startDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"source","in":"query","schema":{"type":"string"}},{"name":"search","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated session list","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array"},"pagination":{"type":"object","properties":{"page":{"type":"number"},"limit":{"type":"number"},"total":{"type":"number"},"totalPages":{"type":"number"}}}}}}}}}}},"/sessions/convert":{"post":{"summary":"Mark session as converted (verified human)","description":"Labels a session as a verified human based on a real conversion event (e.g., purchase, signup). Authenticated via the site key in the Authorization header. This feeds the ground truth system and improves scoring accuracy over time.","tags":["Sessions"],"security":[{"siteKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sessionKey"],"properties":{"sessionKey":{"type":"string","description":"The session key from the ClickLens tag (stored in sessionStorage as cl_sk)"}}}}}},"responses":{"200":{"description":"Session marked as verified human","content":{"application/json":{"schema":{"type":"object","properties":{"sessionKey":{"type":"string"},"verifiedVerdict":{"type":"string","enum":["human"]},"verificationSource":{"type":"string","enum":["conversion"]}}}}}},"400":{"description":"Missing sessionKey in request body"},"401":{"description":"Missing or invalid Authorization header / site key"},"404":{"description":"Session not found for this site"}}}},"/sessions/{id}":{"get":{"summary":"Get session detail","description":"Returns full details for a single session including all scoring signals and flags.","tags":["Sessions"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Session object"},"404":{"description":"Session not found"}}}},"/sources":{"get":{"summary":"Get source breakdown","description":"Returns traffic source analysis with verdict breakdown per source/medium combination.","tags":["Analytics"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"siteId","in":"query","required":true,"schema":{"type":"string"}},{"name":"startDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Source breakdown","content":{"application/json":{"schema":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"medium":{"type":"string"},"total":{"type":"number"},"human":{"type":"number"},"suspect":{"type":"number"},"bot":{"type":"number"},"humanPercent":{"type":"number"},"botPercent":{"type":"number"},"avgScore":{"type":"number","nullable":true}}}}}}}}}}}},"/exclusions":{"get":{"summary":"List exclusion rules","description":"Returns all exclusion rules for a site.","tags":["Exclusions"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"siteId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Exclusion rules","content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"array"}}}}}}}},"post":{"summary":"Create exclusion rule","description":"Creates a new IP, placement, or fingerprint exclusion rule.","tags":["Exclusions"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteId","ruleType","value"],"properties":{"siteId":{"type":"string"},"ruleType":{"type":"string","enum":["ip","placement","fingerprint"]},"value":{"type":"string"},"reason":{"type":"string"}}}}}},"responses":{"201":{"description":"Rule created"}}},"delete":{"summary":"Delete exclusion rule","description":"Deletes an exclusion rule by ID.","tags":["Exclusions"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"}},{"name":"siteId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rule deleted"}}}},"/exclusions/sync":{"post":{"summary":"Sync exclusions to Google Ads","description":"Pushes unsynced IP exclusion rules to Google Ads campaign criteria.","tags":["Exclusions"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["siteId","campaignId"],"properties":{"siteId":{"type":"string"},"campaignId":{"type":"string"}}}}}},"responses":{"200":{"description":"Sync result","content":{"application/json":{"schema":{"type":"object","properties":{"synced":{"type":"number"},"failed":{"type":"number"},"errors":{"type":"array","items":{"type":"string"}}}}}}}}}},"/reports/waste":{"get":{"summary":"Get waste calculator data","description":"Returns estimated ad spend waste based on bot/suspect session percentages per campaign.","tags":["Analytics"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"siteId","in":"query","required":true,"schema":{"type":"string"}},{"name":"startDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Waste report","content":{"application/json":{"schema":{"type":"object","properties":{"googleAdsLinked":{"type":"boolean"},"campaigns":{"type":"array"},"summary":{"type":"object","properties":{"totalSpend":{"type":"number","nullable":true},"totalEstimatedWaste":{"type":"number","nullable":true},"avgBotPercent":{"type":"number"}}}}}}}}}}},"/sites":{"get":{"summary":"List sites","description":"Returns all sites for the authenticated user.","tags":["Sites"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"Site list","content":{"application/json":{"schema":{"type":"object","properties":{"sites":{"type":"array"}}}}}}}},"post":{"summary":"Create site","description":"Registers a new site domain for monitoring.","tags":["Sites"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","example":"example.com"}}}}}},"responses":{"201":{"description":"Site created"}}}},"/sites/{id}/tag":{"get":{"summary":"Get tracking tag snippet","description":"Returns the JavaScript tag snippet for embedding on the site.","tags":["Sites"],"security":[{"supabaseAuth":[]},{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tag snippet","content":{"application/json":{"schema":{"type":"object","properties":{"siteKey":{"type":"string"},"domain":{"type":"string"},"snippet":{"type":"string"}}}}}}}}},"/auth/session":{"get":{"summary":"Get current user session","description":"Returns the authenticated user profile and account details.","tags":["Auth"],"security":[{"supabaseAuth":[]}],"responses":{"200":{"description":"User session","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"accountId":{"type":"string"},"name":{"type":"string"},"plan":{"type":"string","enum":["free","starter","growth","pro"]},"googleAdsLinked":{"type":"boolean"},"stripeCustomerId":{"type":"string","nullable":true}}}}}}}}}}},"/auth/google":{"get":{"summary":"Initiate Google OAuth","description":"Redirects to Google OAuth consent screen for Google Ads linking.","tags":["Auth"],"security":[{"supabaseAuth":[]}],"responses":{"302":{"description":"Redirect to Google OAuth"}}}},"/auth/google/callback":{"get":{"summary":"Google OAuth callback","description":"Handles the OAuth callback from Google, exchanges code for tokens, and links the Google Ads account.","tags":["Auth"],"security":[{"supabaseAuth":[]}],"parameters":[{"name":"code","in":"query","schema":{"type":"string"}},{"name":"error","in":"query","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to settings page"}}}},"/webhooks/stripe":{"post":{"summary":"Stripe webhook handler","description":"Processes Stripe webhook events for subscription lifecycle management. Verified via stripe-signature header.","tags":["Webhooks"],"security":[{"stripeSignature":[]}],"responses":{"200":{"description":"Event processed"},"400":{"description":"Invalid signature"}}}},"/cron/daily-report":{"post":{"summary":"Daily aggregation cron","description":"Aggregates daily session statistics, generates exclusion recommendations, auto-creates exclusion rules for high-confidence bot IPs, and sends weekly reports on Mondays.","tags":["Cron"],"security":[{"cronSecret":[]}],"responses":{"200":{"description":"Processing results"},"401":{"description":"Invalid cron secret"}}}},"/cron/sync-exclusions":{"post":{"summary":"Exclusion sync cron","description":"Syncs unsynced exclusion rules to Google Ads for all linked accounts.","tags":["Cron"],"security":[{"cronSecret":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"campaignId":{"type":"string"}}}}}},"responses":{"200":{"description":"Sync results"},"401":{"description":"Invalid cron secret"}}}},"/api-keys":{"get":{"summary":"List API keys","description":"Returns all API keys for the authenticated account. Requires dashboard (Supabase) authentication.","tags":["API Keys"],"security":[{"supabaseAuth":[]}],"responses":{"200":{"description":"API key list","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"revokedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires Pro plan or dashboard authentication"}}},"post":{"summary":"Create API key","description":"Creates a new API key. Returns the raw key once — it cannot be retrieved again. Maximum 5 active keys per account. Requires dashboard (Supabase) authentication.","tags":["API Keys"],"security":[{"supabaseAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"Production integration"}}}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"rawKey":{"type":"string","description":"Full API key — shown only once"},"createdAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Name required or max keys reached"},"401":{"description":"Not authenticated"},"403":{"description":"Requires Pro plan or dashboard authentication"}}}},"/api-keys/{id}":{"delete":{"summary":"Revoke API key","description":"Revokes an API key by setting revokedAt. The key will no longer authenticate. Requires dashboard (Supabase) authentication.","tags":["API Keys"],"security":[{"supabaseAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Key revoked"},"401":{"description":"Not authenticated"},"403":{"description":"Requires Pro plan or dashboard authentication"},"404":{"description":"API key not found"}}}},"/contact":{"post":{"summary":"Submit contact form","description":"Sends a contact form message from the marketing site.","tags":["Public"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","message"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"message":{"type":"string"}}}}}},"responses":{"200":{"description":"Message sent"},"400":{"description":"Validation error"}}}}},"components":{"securitySchemes":{"supabaseAuth":{"type":"http","scheme":"bearer","description":"Supabase session cookie (managed by middleware)"},"apiKey":{"type":"http","scheme":"bearer","description":"API key with cl_live_ prefix. Available to Pro plan users."},"stripeSignature":{"type":"apiKey","in":"header","name":"stripe-signature","description":"Stripe webhook signature"},"cronSecret":{"type":"http","scheme":"bearer","description":"CRON_SECRET bearer token"},"siteKey":{"type":"http","scheme":"bearer","description":"Site key (found in Site Settings). Used for conversion tracking."}}}}