{"openapi":"3.1.0","info":{"title":"KOLens API","description":"KOLens — TikTok KOL Intelligence API. Search keywords, extract KOL profiles, collect contact info, export reports.","version":"1.0.0"},"paths":{"/healthz":{"get":{"tags":["System"],"summary":"Healthz","description":"Lightweight liveness endpoint for platform health checks (Railway/Render).\n\nNo DB, no Settings, no async dependency — answers the moment the\nHTTP server starts accepting connections. This is what Railway's\nhealthcheckPath in railway.toml points at; if you're tempted to\nadd anything that could block, stop and add it to /readyz instead.\n\nAlso reports the image's git SHA, which is what makes \"did the\nmerge actually go live?\" answerable in one unauthenticated request\ninstead of inferred from a green deploy job.","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthzResponse"}}}}}}},"/readyz":{"get":{"tags":["System"],"summary":"Readyz","description":"Readiness probe — distinct from liveness.\n\nReturns 200 only AFTER the lifespan's background init finished\n(tables created, migrations run, schedulers started). 503 in the\nmeantime. Useful for blue/green deploy systems that wait for full\nreadiness before sending traffic — Railway's liveness check still\npoints at /healthz so a slow background init doesn't kill the\ncontainer, but a load balancer in front can use /readyz to know\nwhen this instance is fully warmed.","operationId":"readyz_readyz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"tags":["System"],"summary":"Health Check","description":"Returns service status. Used by Railway for health monitoring\nand by the Cowork Plugin to verify the API is reachable.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/api/scrape":{"post":{"tags":["Scraping"],"summary":"Submit Scrape Job","description":"Submit a TikTok scrape job. Returns immediately with a job_id.\nPoll GET /api/jobs/{job_id} to track progress.\n\nCredits: pre-charged at submit time based on ``max_videos`` (the\ncost driver — Apify pages fetched). Default formula is\n``ceil(max_videos / 10)`` credits. Insufficient balance returns 402;\nno work is done. If the worker subsequently fails before persisting\nany videos, ``scrape_runner`` refunds the pre-charge.","operationId":"submit_scrape_job_api_scrape_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScrapeRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScrapeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs":{"get":{"tags":["Scraping"],"summary":"List Jobs","description":"List recent scrape jobs.\n\nWhen the Vercel proxy forwards ``X-Kolens-User-Id`` (the normal case\nfor the web app), restrict to jobs owned by that user — via either\ndirect job.user_id (set since the credits-attribution wiring landed)\nor APIKey.user_id (covers legacy rows). Without the header (CLI /\ncowork plugin) we keep the old per-api-key behavior.\n\nEPIC #114 Phase 3: when ``X-Kolens-Workspace-Id`` is set and the\ncaller is a member, jobs are scoped to that workspace's pool\n(every member's job is visible). Forging the header → 403.","operationId":"list_jobs_api_jobs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/JobListItem"},"title":"Response List Jobs Api Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}":{"get":{"tags":["Scraping"],"summary":"Get Job Status","description":"Poll job progress. Check `status` field: queued → running → completed | failed.","operationId":"get_job_status_api_jobs__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/stream":{"get":{"tags":["Scraping"],"summary":"Stream Job Status","description":"Stream job progress via Server-Sent Events. Sends a JSON event every 2s\nuntil the job reaches a terminal state (completed/failed/cancelled).","operationId":"stream_job_status_api_jobs__job_id__stream_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/cancel":{"post":{"tags":["Scraping"],"summary":"Cancel Job","description":"Cancel a queued job or request cancellation for a running job.","operationId":"cancel_job_api_jobs__job_id__cancel_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/events":{"get":{"tags":["Scraping"],"summary":"Get Job Events","description":"Read the persisted execution timeline of a job.","operationId":"get_job_events_api_jobs__job_id__events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/JobEventResponse"},"title":"Response Get Job Events Api Jobs  Job Id  Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/videos":{"get":{"tags":["Scraping"],"summary":"Get Job Videos","description":"List videos discovered by a single job.\n\n``sort``: ``views`` (default) | ``likes`` | ``recent``.\n``role``: optional filter on analyzer's user_role tag\n          (``kol`` | ``seller`` | ``other``).\nOutput is intentionally a slim dict per video — the grid view only\nneeds thumbnail/url/caption/views/author, so we don't pay for full\nVideoOut serialization on 200-row payloads.","operationId":"get_job_videos_api_jobs__job_id__videos_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"views","title":"Sort"}},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"},"title":"Response Get Job Videos Api Jobs  Job Id  Videos Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/export":{"get":{"tags":["Scraping"],"summary":"Export Job Results","description":"Download the job results as an Excel file.\nOnly available when job status is 'completed'.","operationId":"export_job_results_api_jobs__job_id__export_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"xlsx","title":"Format"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols":{"get":{"tags":["KOL Database"],"summary":"List Kols","description":"Query the accumulated KOL database with filters.\nResults are scoped to authors THIS caller's user OR active workspace\nhas discovered — never returns rows surfaced by an unrelated user.\nThe scope is enforced via an EXISTS subquery on ``user_discoveries``\nregardless of the filter combination below.","operationId":"list_kols_api_kols_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"keyword","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by keyword found in their videos","title":"Keyword"},"description":"Filter by keyword found in their videos"},{"name":"job_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to authors discovered by a specific scrape job","title":"Job Id"},"description":"Filter to authors discovered by a specific scrape job"},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"KOL | Seller","title":"Role"},"description":"KOL | Seller"},{"name":"platform","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"tiktok | instagram | x | douyin","title":"Platform"},"description":"tiktok | instagram | x | douyin"},{"name":"min_followers","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Min Followers"}},{"name":"max_followers","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Followers"}},{"name":"require_email","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Require Email"}},{"name":"require_website","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Require Website"}},{"name":"min_avg_views","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Avg Views"}},{"name":"engagement_rate_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"description":"Minimum engagement rate %","title":"Engagement Rate Min"},"description":"Minimum engagement rate %"},{"name":"engagement_rate_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"description":"Maximum engagement rate %","title":"Engagement Rate Max"},"description":"Maximum engagement rate %"},{"name":"avg_views_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Minimum average views","title":"Avg Views Min"},"description":"Minimum average views"},{"name":"avg_views_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Maximum average views","title":"Avg Views Max"},"description":"Maximum average views"},{"name":"min_posts_per_month","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Minimum posts per month (from videos_per_week)","title":"Min Posts Per Month"},"description":"Minimum posts per month (from videos_per_week)"},{"name":"active_within_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":3650,"minimum":1},{"type":"null"}],"description":"Posted at least once in the last N days","title":"Active Within Days"},"description":"Posted at least once in the last N days"},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by country","title":"Country"},"description":"Filter by country"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by category","title":"Category"},"description":"Filter by category"},{"name":"gender","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by gender","title":"Gender"},"description":"Filter by gender"},{"name":"verified_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only verified accounts","default":false,"title":"Verified Only"},"description":"Only verified accounts"},{"name":"has_instagram","in":"query","required":false,"schema":{"type":"boolean","description":"Has Instagram link","default":false,"title":"Has Instagram"},"description":"Has Instagram link"},{"name":"has_youtube","in":"query","required":false,"schema":{"type":"boolean","description":"Has YouTube link","default":false,"title":"Has Youtube"},"description":"Has YouTube link"},{"name":"hide_sellers","in":"query","required":false,"schema":{"type":"boolean","description":"Hide brand / store accounts (is_seller_like=True)","default":false,"title":"Hide Sellers"},"description":"Hide brand / store accounts (is_seller_like=True)"},{"name":"looked_up_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only authors that came in via handle-lookup","default":false,"title":"Looked Up Only"},"description":"Only authors that came in via handle-lookup"},{"name":"max_reach_ratio","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":1.0},{"type":"null"}],"description":"Hide accounts whose avg views exceed N× their followers (reposter / aggregator signal). Try 20.","title":"Max Reach Ratio"},"description":"Hide accounts whose avg views exceed N× their followers (reposter / aggregator signal). Try 20."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"effective_reach | avg_views | followers | engagement_rate | last_updated","default":"effective_reach","title":"Sort By"},"description":"effective_reach | avg_views | followers | engagement_rate | last_updated"},{"name":"scope","in":"query","required":false,"schema":{"type":"string","pattern":"^(personal|team|both)$","description":"Workspace-scope toggle (EPIC #114 Phase 3): personal = only your own discoveries, team = only discoveries from your active workspace, both = the union (default).","default":"both","title":"Scope"},"description":"Workspace-scope toggle (EPIC #114 Phase 3): personal = only your own discoveries, team = only discoveries from your active workspace, both = the union (default)."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":20,"title":"Per Page"}},{"name":"llm_friendly","in":"query","required":false,"schema":{"type":"boolean","description":"Add LLM-friendly pre-digested facets","default":false,"title":"Llm Friendly"},"description":"Add LLM-friendly pre-digested facets"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KOLListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}":{"get":{"tags":["KOL Database"],"summary":"Get Kol Profile","description":"Full KOL profile including contact info and all keywords they appeared under.\n\nScoped: returns 404 if the caller has not discovered this author and\ntheir active workspace hasn't either — same response as if the\nauthor didn't exist at all, so we don't leak \"this row exists but\nisn't yours\" via a 403.\n\nWith ``lookup_if_missing=true`` (EPIC #275), a 404 path triggers a\nsingle-handle on-demand lookup on the chosen ``platform`` and then\nre-queries. Cheap when the row already exists; pricier when it\ndoesn't (one credit per 50 videos at minimum, refunded on failure).","operationId":"get_kol_profile_api_kols__username__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"lookup_if_missing","in":"query","required":false,"schema":{"type":"boolean","description":"If the row isn't in the DB yet, run a single-handle lookup via Apify before responding (EPIC #275). Charges credits + respects the chosen ``platform``. Default false keeps the endpoint cheap for the read-only common case.","default":false,"title":"Lookup If Missing"},"description":"If the row isn't in the DB yet, run a single-handle lookup via Apify before responding (EPIC #275). Charges credits + respects the chosen ``platform``. Default false keeps the endpoint cheap for the read-only common case."},{"name":"platform","in":"query","required":false,"schema":{"type":"string","description":"Platform for the on-demand lookup when lookup_if_missing=true","default":"tiktok","title":"Platform"},"description":"Platform for the on-demand lookup when lookup_if_missing=true"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KOLProfile"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/snapshots":{"get":{"tags":["KOL Database"],"summary":"Get Kol Snapshots","description":"Return historical metric snapshots for a KOL (newest first).","operationId":"get_kol_snapshots_api_kols__username__snapshots_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":30,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SnapshotOut"},"title":"Response Get Kol Snapshots Api Kols  Username  Snapshots Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/videos":{"get":{"tags":["KOL Database"],"summary":"Get Kol Videos","description":"Return top videos for a KOL the caller has discovered (personally\nor via their workspace), sorted by view count descending. Returns\nan empty list when neither the caller nor their workspace has seen\nthe video — matches the 404-style isolation on the profile endpoint\nwithout leaking the existence of someone else's KOL via partial data.","operationId":"get_kol_videos_api_kols__username__videos_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":12,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VideoOut"},"title":"Response Get Kol Videos Api Kols  Username  Videos Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/refresh-contact-from-bio":{"post":{"tags":["KOL Database"],"summary":"Refresh Contact From Bio","description":"Walk the KOL's TikTok bio for URLs (Linktree, personal sites, etc.),\nfetch each, and extract emails / phones / social handles into the\ncontacts row. Free — no external API. Replaces the paid Influencers\nClub call for users who don't have that subscription.","operationId":"refresh_contact_from_bio_api_kols__username__refresh_contact_from_bio_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Contact From Bio Api Kols  Username  Refresh Contact From Bio Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/profile-scrape":{"post":{"tags":["KOL Database"],"summary":"Profile Scrape By Handle","description":"Look up a single TikTok handle and persist their profile + videos.\n\nCharges ``ceil(max_videos / 50)`` credits (so the default 50-video\npull is exactly 1 credit). Refunds the charge if Apify fails or\nreturns nothing usable.\n\nReturns the headline metrics from the persisted DBAuthor row;\nthe full profile is then available via ``GET /api/kols/{username}``.","operationId":"profile_scrape_by_handle_api_kols_profile_scrape_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandleLookupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandleLookupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/bulk-handle-import":{"post":{"tags":["KOL Database"],"summary":"Bulk Handle Import Route","description":"Bulk-enrich N TikTok handles in one call.","operationId":"bulk_handle_import_route_api_kols_bulk_handle_import_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkHandleImportRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkHandleImportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/similar":{"get":{"tags":["KOL Database"],"summary":"Get Similar Creators","description":"Return creators similar to ``username`` — ranked by co-mention,\nsame-category + similar follower tier, and shared hashtags.","operationId":"get_similar_creators_api_kols__username__similar_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":5,"default":20,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimilarCreatorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/audience":{"get":{"tags":["KOL Database"],"summary":"Get Kol Audience","description":"Return the latest audience snapshot for ``username``, or None if\nwe don't have one yet. ``history > 1`` returns a list ordered newest\nfirst — feeds the drift chart in AP-3.","operationId":"get_kol_audience_api_kols__username__audience_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"history","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"N of most recent snapshots","default":1,"title":"History"},"description":"N of most recent snapshots"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["KOL Database"],"summary":"Create Kol Audience","description":"Run a fresh audience sample. Pre-charges credits via the same\npattern as ``/api/kols/profile-scrape`` and refunds on hard failure.","operationId":"create_kol_audience_api_kols__username__audience_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceSnapshotRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceSnapshotOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/audience/refresh":{"post":{"tags":["KOL Database"],"summary":"Refresh Kol Audience","description":"Synchronous audience refresh — sample + aggregate + persist.\n\nPlatform is inferred from ``DBAuthor.platform`` (falls back to\n``tiktok``). Identical credit-pre-charge + refund semantics as the\nPOST /audience endpoint — uses the same private ``_AUDIENCE_PRICE_BY_SIZE``\ntable so a refresh costs exactly what a fresh snapshot does.","operationId":"refresh_kol_audience_api_kols__username__audience_refresh_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceSnapshotRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceSnapshotOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/provenance":{"get":{"tags":["KOL Database"],"summary":"Get Kol Provenance","description":"How this KOL entered the calling user's view.\n\nJoins DBUserDiscovery (the per-user share row) → DBSearchJob (the\njob that surfaced them) → DBVideo (count of videos this author had\nin that specific job). One ProvenanceSource per discovery event,\nmost-recent first.","operationId":"get_kol_provenance_api_kols__username__provenance_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvenanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/cross-platform":{"get":{"tags":["KOL Database"],"summary":"Get Kol Cross Platform","description":"Aggregate everything we know about this handle across platforms.\n\nTwo sources for \"linked\" rows:\n  1. A DBAuthor with the same ``username`` on a different platform\n     (means we've actually scraped them there).\n  2. ``DBContact.instagram`` / ``.youtube`` strings on the primary\n     row — pointers we know about but haven't scraped, surfaced so\n     the user can click out + know to add a discovery plan for it.","operationId":"get_kol_cross_platform_api_kols__username__cross_platform_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrossPlatformResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/outreach-history":{"get":{"tags":["KOL Database"],"summary":"Get Kol Outreach History","description":"All outreach activity for this KOL in the caller's active workspace.\n\nReturns the conversation row (if any) + its event timeline. Empty\nresponse (conversation_id=None, events=[]) means no outreach has\nhappened — the dossier UI renders a \"Start outreach\" CTA in this\ncase rather than an empty timeline.","operationId":"get_kol_outreach_history_api_kols__username__outreach_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/lists":{"get":{"tags":["KOL Database"],"summary":"Get Kol List Memberships","description":"Lists owned by (or shared with) the caller that already contain\nthis KOL. Used by the profile header to surface \"already in: A, B\".\n\nScope rules mirror the GET /api/lists endpoint:\n  - Lists where ``user_id`` matches the resolved caller, OR\n  - Lists where ``workspace_id`` matches the caller's active workspace","operationId":"get_kol_list_memberships_api_kols__username__lists_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/_KolListMembership"},"title":"Response Get Kol List Memberships Api Kols  Username  Lists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/activity-summary":{"get":{"tags":["KOL Database"],"summary":"Get Kol Activity Summary","description":"EPIC #208 Phase 1 — what's this KOL up to lately.\n\nCache-first (24h TTL); ``refresh=true`` forces a fresh LLM call\nand charges 1 credit. Authors with no recent videos return a\n\"no data\" response without burning a credit (so a later video\nimport immediately benefits).","operationId":"get_kol_activity_summary_api_kols__username__activity_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","pattern":"^(7d|30d|90d)$","default":"7d","title":"Period"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force fresh LLM call + cache replace","default":false,"title":"Refresh"},"description":"Force fresh LLM call + cache replace"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivitySummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/overview":{"get":{"tags":["KOL Database"],"summary":"Get Kol Overview","description":"Holistic creator overview — the partnership manager's first read.\n\nCache-first (30-day TTL); ``refresh=true`` forces a fresh LLM call\nand charges 1 credit. Authors with zero scraped videos return a\n\"not enough data\" overview without burning a credit.","operationId":"get_kol_overview_api_kols__username__overview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force fresh LLM call + cache replace","default":false,"title":"Refresh"},"description":"Force fresh LLM call + cache replace"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverviewSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/background":{"get":{"tags":["KOL Database"],"summary":"Get Kol Background","description":"Web-grounded background report on the creator.\n\nOne of only two AI features that charge (founder-confirmed 2026-05\n\"cheap free, expensive charged\" policy; the other is deep-overview).\nThe search-tool LLM call is the genuinely expensive bit, so a fresh\ngeneration costs 1 credit (``settings.kol_background_research_cost_credits``).\nCache-first with a 30-day TTL: a cache hit (same locale, within TTL)\nis served free and never debits. ``refresh=true`` forces a fresh\nsearch-LLM call and always charges.\n\nCharge-then-generate semantics, mirroring deep-overview / activity-\nsummary:\n  - 402 if the combined workspace + personal balance can't cover it.\n  - On AI-gateway failure after the debit we refund the workspace\n    half (the only side with a write path); the cache row is never\n    written, so a retry against a fixed gateway works normally.","operationId":"get_kol_background_api_kols__username__background_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force fresh search + cache replace","default":false,"title":"Refresh"},"description":"Force fresh search + cache replace"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackgroundResearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/deep-overview":{"get":{"tags":["KOL Database"],"summary":"Get Kol Deep Overview Cached","description":"Cache-only read for the deep analysis. Returns ``null`` (HTTP 200)\nwhen there's nothing cached — the dossier card uses that to flip its\nbutton from \"View deep analysis\" to \"Run deep analysis (2 credits)\".\n\nNever charges. The POST counterpart at the same path runs the\nanalysis and bills the workspace / personal credit pool.","operationId":"get_kol_deep_overview_cached_api_kols__username__deep_overview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DeepOverviewResponse"},{"type":"null"}],"title":"Response Get Kol Deep Overview Cached Api Kols  Username  Deep Overview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["KOL Database"],"summary":"Run Kol Deep Overview","description":"Run the premium deep analysis and charge the workspace pool\n(falling back to personal credits via ``credits.charge``). Cost is\ngoverned by ``settings.deep_analysis_cost_credits`` (default 2).\n\nCharge-then-generate semantics:\n  - 402 if the combined workspace + personal balance can't cover it.\n  - On a successful charge we always attempt the LLM call. If the\n    gateway 503s after the debit, we refund the workspace half\n    immediately (personal-side refunds aren't possible without\n    Supabase write paths, so we surface a clear 503 detail and the\n    operator can mail-in support — same UX as the scrape route).","operationId":"run_kol_deep_overview_api_kols__username__deep_overview_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeepOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/outreach":{"get":{"tags":["KOL Database"],"summary":"Get Kol Outreach Cached","description":"Cache-only read for the cold-outreach draft. Returns 200 with\n``null`` body when nothing's cached — the OutreachPanel uses that\nsignal to render the \"Generate with AI\" CTA instead of an empty\ntemplate.\n\nNever calls the LLM. ``POST`` at the same path is the\ngenerate-or-refresh entry point.","operationId":"get_kol_outreach_cached_api_kols__username__outreach_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/OutreachDraftResponse"},{"type":"null"}],"title":"Response Get Kol Outreach Cached Api Kols  Username  Outreach Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["KOL Database"],"summary":"Post Kol Outreach Generate","description":"Generate-or-refresh the cold-outreach draft.\n\nPre-2026-05-18 this route lived on Vercel and burned an LLM call on\nevery operator click. Now cache-first per (workspace, username,\nlocale) with 30-day TTL; ``refresh=true`` forces a fresh generation.","operationId":"post_kol_outreach_generate_api_kols__username__outreach_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force fresh LLM call + cache replace","default":false,"title":"Refresh"},"description":"Force fresh LLM call + cache replace"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachDraftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/outreach/translate":{"post":{"tags":["KOL Database"],"summary":"Post Kol Outreach Translate","description":"Translate the operator's current EN draft into ``target_locale``\nfor display below the editor. Display-only artefact — not cached\non this side, not charged. The OutreachPanel calls this after every\nGenerate / Refresh / Rewrite, plus on demand when the operator\nedits the body manually and hits the Refresh-translation button.","operationId":"post_kol_outreach_translate_api_kols__username__outreach_translate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachTranslateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachTranslateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/outreach/rewrite":{"post":{"tags":["KOL Database"],"summary":"Post Kol Outreach Rewrite","description":"Rewrite the cached EN outreach draft per the operator's\ninstruction (e.g. \"make it more casual\", \"mention their latest\nproduct review\"). Reads the existing draft from cache, runs one\nLLM call, upserts the rewritten draft back into the same row.\nCosts ``settings.outreach_rewrite_cost_credits`` (default 1).\n\nCharge-then-generate semantics (same pattern as deep-overview):\n  - 402 if combined workspace + personal balance can't cover it.\n  - On AIGatewayError after the debit, the workspace half is\n    refunded immediately; the personal half — if any — needs the\n    operator to mail-in support.","operationId":"post_kol_outreach_rewrite_api_kols__username__outreach_rewrite_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachRewriteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachDraftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/audience/insights":{"post":{"tags":["KOL Database"],"summary":"Post Kol Audience Insights","description":"AI interpretation of the audience snapshot — cache-first.\n\nSame caching story as ``/kols/{u}/outreach``: pre-2026-05-18 this\nran the LLM on every \"Interpret\" click. Now cached per (workspace,\nusername, locale) and gated on a SHA-256 of the snapshot payload so\na re-sampled audience invalidates the cached interpretation.","operationId":"post_kol_audience_insights_api_kols__username__audience_insights_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","description":"Output locale — en / zh-CN / ja","default":"en","title":"Locale"},"description":"Output locale — en / zh-CN / ja"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force fresh LLM call + cache replace","default":false,"title":"Refresh"},"description":"Force fresh LLM call + cache replace"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceInsightsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceInsightsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/enrich-contact":{"post":{"tags":["Enrichment"],"summary":"Enrich Contact","description":"Find + verify a deliverable business email for a creator.\n\nPay-for-result: a deliverable email costs\n``contact_enrichment_cost_credits`` (default 1); a clean miss or a\ncached repeat is free. 402 if the caller can't cover the cost before\nwe spend an upstream call.","operationId":"enrich_contact_api_kols__username__enrich_contact_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_enrich_contact_api_kols__username__enrich_contact_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/cross-platform/resolve":{"post":{"tags":["Enrichment"],"summary":"Cross Platform Reach","description":"Resolve a creator's other-platform profiles and merge them into a\nsingle cross-platform reach view.\n\nThe paid sibling of the free ``GET /kols/{username}/cross-platform``\ndossier aggregation (which only surfaces pointers we already know):\nthis POST actively fetches each other-platform profile via Apify and\nmerges real follower counts. Pay-for-result — charges\n``cross_platform_cost_credits`` (default 1) per platform actually\nresolved; unresolved platforms never debit; a cached repeat within the\nfreshness window is free.","operationId":"cross_platform_reach_api_kols__username__cross_platform_resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrossPlatformRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrossPlatformResolveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists":{"post":{"tags":["KOL Lists"],"summary":"Create List","description":"Create a new named KOL library. When called with an active\nworkspace header, the list is stamped with that workspace_id so\nevery teammate can read/write it (EPIC #114 Phase 3).","operationId":"create_list_api_lists_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["KOL Lists"],"summary":"Get Lists","description":"Return KOL lists owned by the calling user (or visible to the\ncaller's active workspace), each with a compact decision ``rollup``.\n\nWith ``X-Kolens-User-Id`` (the normal web-app path), filter to lists\nwhose api_key resolves to that user via APIKey.user_id. Without it\n(CLI / cowork plugin), fall back to per-api-key match. When an\nactive workspace is supplied, lists owned by the workspace are\nalso visible (EPIC #114 Phase 3).\n\nGitHub #376 — every list carries a ``rollup``\n``{member_count, n_shortlisted, n_untriaged, n_passed, n_red_flag}``\nso the portfolio/index view can render \"Skincare Q3 · 40 KOL · 8\nshortlisted · 12 untriaged · 6 red-flag\" without a per-list call.","operationId":"get_lists_api_lists_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ListOut"},"title":"Response Get Lists Api Lists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}":{"get":{"tags":["KOL Lists"],"summary":"Get List","operationId":"get_list_api_lists__list_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["KOL Lists"],"summary":"Update List","description":"Update mutable list-level settings.\n\nIssue #391 — currently the only mutable field is\n``enable_daily_monitoring``, the daily-snapshot opt-out flag. The\nlist-detail UI's toggle calls this endpoint when the operator flips\nthe switch. Partial updates only: keys absent from the body are\nleft untouched on the persisted row.","operationId":"update_list_api_lists__list_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["KOL Lists"],"summary":"Delete List","operationId":"delete_list_api_lists__list_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}/members":{"post":{"tags":["KOL Lists"],"summary":"Add Members","description":"Add KOLs to the list by username.\nSilently skips usernames not found in the DB or already in the list.\nReturns counts of added / skipped / not_found.","operationId":"add_members_api_lists__list_id__members_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/api__schemas__lists__AddMembersRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Add Members Api Lists  List Id  Members Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["KOL Lists"],"summary":"Get Members","description":"Return the list as a single decision surface (GitHub #376).\n\nONE call yields everything the decision UI needs: per-member identity,\ncore stats, a 0-100 fit score + red/positive flags, growth trend,\ntier + price range, contact completeness, the list-local\n``triage_state``, and the CRM-derived ``outreach`` block (null when\nthe member has no conversation). ``entries`` is sorted by\n``fit.score`` descending — the strongest candidate first — and a\ntop-level ``rollup`` summarises the batch.\n\nThe active workspace is resolved so the CRM outreach join + the\ncached brand-fit recommendation score can be included. If resolution\nfails the surface still returns — it just omits the workspace-scoped\nfields rather than collapsing the whole view.","operationId":"get_members_api_lists__list_id__members_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}/enrich-contacts":{"post":{"tags":["KOL Lists"],"summary":"Enrich List Contacts","description":"Find + verify a deliverable email for each member of the list.\n\nPay-for-result per member (workspace-first billing). Returns per-member\noutcomes plus a rollup; ``stopped_insufficient_credits`` is True when\nthe loop halted because the next hit couldn't be paid for.","operationId":"enrich_list_contacts_api_lists__list_id__enrich_contacts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchEnrichRequest","default":{"only_missing":true,"limit":50}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Enrich List Contacts Api Lists  List Id  Enrich Contacts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}/members/{username}":{"patch":{"tags":["KOL Lists"],"summary":"Update Member","description":"Update the list-local triage state, tags, or notes for a member.\n\nGitHub #376 — ``triage_state`` (``untriaged`` | ``shortlisted`` |\n``passed``) is the operator's per-list evaluation verdict. Outreach\nfunnel status is NOT settable here: it lives in the CRM\n(``DBKolConversation``) and the decision surface reads it from there.","operationId":"update_member_api_lists__list_id__members__username__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Update Member Api Lists  List Id  Members  Username  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["KOL Lists"],"summary":"Remove Member","operationId":"remove_member_api_lists__list_id__members__username__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}/refresh":{"post":{"tags":["KOL Lists"],"summary":"Refresh Snapshots","description":"Capture a new metric snapshot for every member in the list.\nCall this periodically (e.g. weekly) to build trend data.\nReturns the count of snapshots taken.","operationId":"refresh_snapshots_api_lists__list_id__refresh_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Snapshots Api Lists  List Id  Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}/assign-export":{"post":{"tags":["KOL Lists"],"summary":"Assign Export","description":"Slice this list across operators and return a single .xlsx with one\nsheet per operator + a Summary tab. No state is mutated.","operationId":"assign_export_api_lists__list_id__assign_export_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sync/feishu/{list_id}":{"post":{"tags":["Sync"],"summary":"Sync To Feishu","description":"Export a KOL list to Feishu Bitable.\n\nBehaviour:\n- If no ``app_token`` is provided a brand-new Bitable is created and its\n  URL is returned so you can open it directly.\n- If ``app_token`` (and optionally ``table_id``) are provided the records\n  are appended to that existing Bitable / table.\n- Each KOL becomes one row: username, stats, tier, price estimate,\n  outreach status, contact info (email / IG / YouTube / website), and\n  growth trend.\n\nRequires ``FEISHU_APP_ID`` and ``FEISHU_APP_SECRET`` to be set in the\nKOLens API environment (obtain from https://open.feishu.cn/app — enable\nscopes ``bitable:app:create`` and ``bitable:record:write``).","operationId":"sync_to_feishu_api_sync_feishu__list_id__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeishuSyncBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeishuSyncResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{list_id}/export":{"get":{"tags":["Sync"],"summary":"Export List Excel","description":"Download a KOL list as an Excel (.xlsx) file.\n\nNo external services required — generated server-side with openpyxl.\nThe file can be opened directly in Excel or imported into Google Sheets.","operationId":"export_list_excel_api_lists__list_id__export_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sync/gsheets/{list_id}":{"post":{"tags":["Sync"],"summary":"Sync To Gsheets","description":"Export a KOL list to a new Google Sheet.\n\nCreates a spreadsheet in the service account's Drive, writes all KOL\nmembers as rows with headers, freezes the header row, and optionally\nshares the sheet with the caller's email.\n\nRequires ``GOOGLE_SA_JSON`` to be set in the KOLens API environment\n(the full JSON contents of a Google service account key file).\nThe service account must have the Google Sheets API and Google Drive\nAPI enabled in its project.","operationId":"sync_to_gsheets_api_sync_gsheets__list_id__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSheetsSyncBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSheetsSyncResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cookies/upload":{"post":{"tags":["cookies"],"summary":"Upload Cookie File","description":"Upload a new cookie file for authenticated TikTok scraping.\n\nThe cookies should be in Playwright-compatible format with fields:\n- name, value, domain, path, expires, httpOnly, secure, sameSite\n\nEmpty cookie arrays and missing filenames are rejected with 422 via the\nschema's Field constraints — see api/schemas/cookies.py.","operationId":"upload_cookie_file_api_cookies_upload_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CookieUploadRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CookieFileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/cookies":{"get":{"tags":["cookies"],"summary":"List Cookie Files","description":"List all cookie files for the current API key.","operationId":"list_cookie_files_api_cookies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CookieListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/cookies/{cookie_id}":{"get":{"tags":["cookies"],"summary":"Get Cookie File","description":"Get detailed information about a specific cookie file, including the cookie data.","operationId":"get_cookie_file_api_cookies__cookie_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"cookie_id","in":"path","required":true,"schema":{"type":"string","title":"Cookie Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Cookie File Api Cookies  Cookie Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["cookies"],"summary":"Delete Cookie File","description":"Delete a cookie file.","operationId":"delete_cookie_file_api_cookies__cookie_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"cookie_id","in":"path","required":true,"schema":{"type":"string","title":"Cookie Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cookies/{cookie_id}/activate":{"put":{"tags":["cookies"],"summary":"Activate Cookie File","description":"Activate a cookie file for use in scraping.\n\nThis will deactivate all other cookie files for the current API key.","operationId":"activate_cookie_file_api_cookies__cookie_id__activate_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"cookie_id","in":"path","required":true,"schema":{"type":"string","title":"Cookie Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CookieFileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/keys":{"post":{"tags":["Admin"],"summary":"Create Api Key","description":"Generate a new API key.\nCall this once per new user/subscriber.\nRequires X-Admin-Secret header matching SECRET_KEY env var.","operationId":"create_api_key_admin_keys_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Admin"],"summary":"List Api Keys","description":"List API keys, optionally scoped to a single user.\n\nReturns the keys with the raw secret REDACTED — we keep the first\n8 chars (``kol_xxxxxxxx``) for visual matching and a short suffix,\nnothing more. The full key was returned exactly once at creation\ntime; the operator (or in the user-facing UX, the user) was meant\nto copy it then. Lost keys must be revoked + replaced.","operationId":"list_api_keys_admin_keys_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one user's keys","title":"User Id"},"description":"Filter to one user's keys"},{"name":"include_revoked","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Revoked"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Api Keys Admin Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/keys/{key_id}":{"delete":{"tags":["Admin"],"summary":"Revoke Api Key","description":"Soft-delete: mark the key inactive. Bearer-auth checks already\nfilter on ``is_active = True``, so a revoke takes effect on the\nnext request. We never hard-delete because the row is referenced\nby ``DBSearchJob.api_key`` / ``DBGrowthAlert.api_key`` and\ncascading the FK is more dangerous than keeping a tombstone.","operationId":"revoke_api_key_admin_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/credits/diagnostics":{"get":{"tags":["Admin"],"summary":"Credits Diagnostics","description":"Reports the current Supabase config + a live reachability probe.\n\nReturns the env-var shape (URL host, key length, JWT-shape check),\nplus the actual HTTP status from a real GET against Supabase.","operationId":"credits_diagnostics_admin_credits_diagnostics_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/tracked-videos":{"get":{"tags":["Admin"],"summary":"List Tracked Videos","description":"Return all tracked videos (newest first) with snapshot/comment\ncounts and the owner's email. Read-only operator surface.","operationId":"list_tracked_videos_admin_tracked_videos_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one user's tracked videos","title":"User Id"},"description":"Filter to one user's tracked videos"},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Active Only"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Tracked Videos Admin Tracked Videos Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/r2/diagnostics":{"get":{"tags":["Admin"],"summary":"R2 Diagnostics","description":"Returns the redacted R2 config + a live HeadBucket probe.","operationId":"r2_diagnostics_admin_r2_diagnostics_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response R2 Diagnostics Admin R2 Diagnostics Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/data-isolation/backfill":{"post":{"tags":["Admin"],"summary":"Data Isolation Backfill","description":"Reverse-map shared scraped data into per-user discovery rows.\n\nWalks ``search_jobs`` ordered ascending by ``created_at`` — older\njobs win, so the first user who surfaced a given author keeps the\nearliest ``first_seen_at``.\n\nReturns counts (videos / authors / contacts seen, discoveries\nwritten). ``dry_run=true`` short-circuits the write — useful to\nsize the migration before pulling the trigger.","operationId":"data_isolation_backfill_admin_data_isolation_backfill_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Data Isolation Backfill Admin Data Isolation Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/rankings/refresh":{"post":{"tags":["Admin"],"summary":"Refresh Creator Rankings","description":"Start a rebuild of one country board, or every supported market.\n\nReturns as soon as the work is scheduled. A single board is a\nsynchronous Apify run that can take minutes, and the full sweep runs\nthem back to back — far longer than the serverless proxy in front of\nthis endpoint will hold a connection open. Waiting inline is what\nmade the admin panel's first attempt fail with a bare \"Network\nerror\" while the run itself was healthy. Poll\n``GET /admin/rankings/status`` for progress.\n\nEach board is replaced wholesale — a creator who fell off the\nleaderboard has to disappear from it — but a fetch that comes back\nempty leaves the existing board alone rather than blanking a live\npage.","operationId":"refresh_creator_rankings_admin_rankings_refresh_post","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},{"name":"window","in":"query","required":false,"schema":{"type":"string","default":"7d","title":"Window"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Creator Rankings Admin Rankings Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/rankings/status":{"get":{"tags":["Admin"],"summary":"Creator Rankings Status","description":"Progress of the in-flight refresh plus the boards actually stored.\n\nThe stored counts are the authoritative half: the in-process progress\nis lost on deploy, but the rows are not.","operationId":"creator_rankings_status_admin_rankings_status_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Creator Rankings Status Admin Rankings Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/alerts/backfill-preferences":{"post":{"tags":["Admin"],"summary":"Backfill Alert Preferences","description":"Give every tracking/watchlist owner a default alert channel.\n\nReturns ``{\"candidates\", \"created\", \"skipped_existing\", \"dry_run\"}``.","operationId":"backfill_alert_preferences_admin_alerts_backfill_preferences_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Backfill Alert Preferences Admin Alerts Backfill Preferences Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/tracking/paused":{"get":{"tags":["Admin"],"summary":"Tracking Paused Status","description":"Who currently has tracking frozen for an empty wallet.\n\nRead-only companion to the notice sweep — answers \"is anyone sitting\nstalled right now, and have they been told\" without sending anything.","operationId":"tracking_paused_status_admin_tracking_paused_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Tracking Paused Status Admin Tracking Paused Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/tracking/credit-notice":{"post":{"tags":["Admin"],"summary":"Send Tracking Credit Notice","description":"Run the out-of-credits notice sweep now.\n\nThe scheduler runs this on its own every\n``TRACKING_CREDIT_NOTICE_SWEEP_HOURS``; this endpoint exists so an\noperator can reach users who are *already* stalled without waiting\nfor the next sweep.\n\n``force=true`` ignores the per-user cooldown — for the first run\nafter deploy, or to re-send after fixing a bad address. Use it\nknowingly: it will mail everyone currently paused.","operationId":"send_tracking_credit_notice_admin_tracking_credit_notice_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Send Tracking Credit Notice Admin Tracking Credit Notice Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/data-isolation/backfill-workspaces":{"post":{"tags":["Admin"],"summary":"Data Isolation Backfill Workspaces","description":"Stamp ``workspace_id`` on legacy ``user_discoveries`` rows.\n\nFor every row with ``workspace_id IS NULL``, sets it to the\ndiscovering user's Personal workspace (created on demand). Safe to\nrun twice — the second pass updates zero rows.\n\nReturns counts: ``rows_examined``, ``rows_updated``, ``users_seen``,\n``dry_run``. ``dry_run=true`` counts what WOULD be written without\nwriting — same shape as ``/data-isolation/backfill``.","operationId":"data_isolation_backfill_workspaces_admin_data_isolation_backfill_workspaces_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Data Isolation Backfill Workspaces Admin Data Isolation Backfill Workspaces Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/translate-pending":{"post":{"tags":["Admin"],"summary":"Translate Pending Kol Fields","description":"Translate up to ``limit`` pending KOLs into zh-CN.\n\nReturns a small summary: ``scanned`` (rows examined), ``translated``\n(rows that got at least one new zh field), ``bypassed_cjk`` (rows\nwhose source was already CJK — copied without an LLM call),\n``llm_calls`` (gateway requests made), ``model`` (provider/model\nstring used), ``error`` (set when the LLM call failed and the\nremaining batches were skipped; ``translated`` reflects partial\nprogress before the failure).","operationId":"translate_pending_kol_fields_admin_translate_pending_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"batch_size","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":25,"title":"Batch Size"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Translate Pending Kol Fields Admin Translate Pending Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/cache-avatars-pending":{"post":{"tags":["Admin"],"summary":"Cache Avatars Pending","description":"Cache up to ``limit`` pending KOL avatars from upstream CDN to R2.\n\nReturns a small summary: ``scanned`` (rows examined),\n``cached`` (successful uploads), ``failed`` (download / upload\nerrors — those rows tick their ``avatar_cache_attempts`` counter;\nafter 3 failures they exit the queue), ``bytes_uploaded`` (sum of\npayload sizes across the batch), ``r2_configured`` (false when env\nvars are missing — short-circuits without touching the DB), and\n``error`` (set when the early-exit fired).","operationId":"cache_avatars_pending_admin_cache_avatars_pending_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Cache Avatars Pending Admin Cache Avatars Pending Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/cache-thumbnails-pending":{"post":{"tags":["Admin"],"summary":"Cache Thumbnails Pending","description":"Cache up to ``limit`` pending video thumbnails to R2.","operationId":"cache_thumbnails_pending_admin_cache_thumbnails_pending_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Cache Thumbnails Pending Admin Cache Thumbnails Pending Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/thumbnail-cache-coverage":{"get":{"tags":["Admin"],"summary":"Thumbnail Cache Coverage","description":"Coverage snapshot for the video-thumbnail R2 cache.\n\n``total`` counts videos that have a ``thumbnail_url`` (the cacheable\nuniverse); ``cached`` / ``pending`` / ``exhausted`` partition it; and\n``coverage_pct`` is cached/total. Read-only — safe to poll.","operationId":"thumbnail_cache_coverage_admin_thumbnail_cache_coverage_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Thumbnail Cache Coverage Admin Thumbnail Cache Coverage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/refresh-creator/{username}":{"post":{"tags":["Admin"],"summary":"Refresh Creator Endpoint","description":"Re-scrape one creator from Apify and cache fresh URLs to R2.","operationId":"refresh_creator_endpoint_admin_refresh_creator__username__post","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"default":"tiktok","title":"Platform"}},{"name":"max_videos","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":30,"title":"Max Videos"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Creator Endpoint Admin Refresh Creator  Username  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/reset-author-refresh-cooldown":{"post":{"tags":["Admin"],"summary":"Reset Author Refresh Cooldown","description":"NULL ``DBAuthor.last_refresh_at`` for any author stamped within\nthe last ``hours_ago`` hours so the next recovery-sweep run can\nre-pick them up.","operationId":"reset_author_refresh_cooldown_admin_reset_author_refresh_cooldown_post","parameters":[{"name":"hours_ago","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":24,"title":"Hours Ago"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reset Author Refresh Cooldown Admin Reset Author Refresh Cooldown Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/classify-authors-pending":{"post":{"tags":["Admin"],"summary":"Classify Authors Pending","description":"Classify up to ``limit`` authors with the country + seller-flag\nheuristics. Returns a small summary so the operator can decide\nwhether to keep draining.","operationId":"classify_authors_pending_admin_classify_authors_pending_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":200,"title":"Limit"}},{"name":"all_authors","in":"query","required":false,"schema":{"type":"boolean","description":"When true, re-classify every author (ignoring classified_at). Otherwise only unclassified rows.","default":false,"title":"All Authors"},"description":"When true, re-classify every author (ignoring classified_at). Otherwise only unclassified rows."},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Classify Authors Pending Admin Classify Authors Pending Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/migrations":{"get":{"tags":["Admin"],"summary":"List Schema Migrations","description":"Return all migration files + applied state. Bootstraps the tracking\ntable on first call so the page works on an unseeded DB.\n\n``target`` selects the database. KOLens has two: the app's own\nPostgres and Supabase (auth / profiles / credits / assistant). The\nSupabase tab needs ``SUPABASE_DB_URL``; without it we return\n``configured: false`` and an empty list rather than erroring, so the\nUI can render a setup hint.","operationId":"list_schema_migrations_admin_migrations_get","parameters":[{"name":"target","in":"query","required":false,"schema":{"type":"string","description":"Which database: 'app' (Neon, DATABASE_URL) or 'supabase'.","default":"app","title":"Target"},"description":"Which database: 'app' (Neon, DATABASE_URL) or 'supabase'."},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Schema Migrations Admin Migrations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/migrations/apply":{"post":{"tags":["Admin"],"summary":"Apply Schema Migration","description":"Apply one migration file end-to-end. Idempotent: a second call with\nthe same filename returns ``already_applied=true`` and does no work.\n\nReturns ``error`` (non-null) if the SQL execution failed — the\nresponse wire is still 200 so the UI can show the message inline.\nThe whole file rolls back on any error; the tracking row is only\ninserted after every statement succeeds.","operationId":"apply_schema_migration_admin_migrations_apply_post","parameters":[{"name":"x-admin-user","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-User"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyMigrationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Apply Schema Migration Admin Migrations Apply Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/migrations/mark-applied":{"post":{"tags":["Admin"],"summary":"Mark Schema Migration Applied","description":"Mark one migration as applied WITHOUT running its SQL.\n\nEscape hatch for migrations that contain DML where the data side\nhas already run historically (e.g. 004_add_job_videos_mapping.sql\nhas an INSERT … SELECT backfill that ran once in prod months ago).\nThe auto-detector keeps these PENDING out of safety; this endpoint\nlets the operator stamp the tracking row directly.\n\nSame shape + idempotency as ``apply``: a second call returns\n``already_applied=true``. ``applied_by`` defaults to\n``\"manually-marked\"`` so a future audit of the table shows the\nprovenance distinct from ``\"auto-detected\"`` and ``\"admin\"``.","operationId":"mark_schema_migration_applied_admin_migrations_mark_applied_post","parameters":[{"name":"x-admin-user","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-User"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyMigrationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Mark Schema Migration Applied Admin Migrations Mark Applied Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/email-test/send-outreach":{"post":{"tags":["Admin / Email test"],"summary":"Send Test Outreach","description":"Send a test outreach email through the real SendGrid pipeline.\n\nThe recipient (your own inbox usually) gets a normal-looking email\nwhose ``Reply-To`` is ``reply+<conversation_id>@<INBOUND_HOSTNAME>``.\nReply from your inbox normally; SendGrid Inbound Parse routes the\nreply back to ``POST /api/crm/inbound`` and it lands on this\nconversation's timeline. Use ``GET /admin/email-test/<conv>/events``\nto verify the round-trip.","operationId":"send_test_outreach_admin_email_test_send_outreach_post","parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-user-name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Name"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendOutreachRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendOutreachResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/email-test/simulate-inbound":{"post":{"tags":["Admin / Email test"],"summary":"Simulate Inbound Reply","description":"Inject a synthetic inbound reply WITHOUT going through SendGrid.\n\nUseful when DNS / SendGrid dashboard isn't set up yet, or for fast\niteration. Synthesises a SendGrid-shaped payload and calls\n``ingest_inbound_email`` directly with a fresh\n``sendgrid_message_id`` so we don't trip the dedupe gate.","operationId":"simulate_inbound_reply_admin_email_test_simulate_inbound_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateInboundRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateInboundResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/email-test/conversations":{"get":{"tags":["Admin / Email test"],"summary":"List Test Conversations","description":"List recent ``kol_platform='test'`` conversations belonging to\nthe admin's workspace. Most recent activity first.","operationId":"list_test_conversations_admin_email_test_conversations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TestConversationOut"},"title":"Response List Test Conversations Admin Email Test Conversations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/email-test/{conversation_id}/events":{"get":{"tags":["Admin / Email test"],"summary":"List Test Events","description":"List events on a test conversation. Used by the admin page to\npoll for incoming replies after the operator hits send.","operationId":"list_test_events_admin_email_test__conversation_id__events_get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TestEventOut"},"title":"Response List Test Events Admin Email Test  Conversation Id  Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs":{"get":{"tags":["Admin"],"summary":"List Jobs","description":"Paginated job list with multi-select filters.\n\nPagination is cursor-based on ``created_at DESC``: pass the\n``created_at`` of the last visible row back as ``cursor`` to fetch\nthe next page. Cleaner than offset pagination for a long-running\ntable where new rows constantly land at the top.","operationId":"list_jobs_admin_jobs_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated: queued,running,completed,failed,cancelled","title":"Status"},"description":"Comma-separated: queued,running,completed,failed,cancelled"},{"name":"data_source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated platforms (scraper, apify, tiktok, ...)","title":"Data Source"},"description":"Comma-separated platforms (scraper, apify, tiktok, ...)"},{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"created_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp lower bound on created_at","title":"Created After"},"description":"ISO timestamp lower bound on created_at"},{"name":"keyword_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keyword Contains"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"created_at ISO of last seen row (paginate older)","title":"Cursor"},"description":"created_at ISO of last seen row (paginate older)"},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Jobs Admin Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/stats":{"get":{"tags":["Admin"],"summary":"Get Jobs Stats","description":"Aggregate counters for the dashboard header.\n\nReturns:\n  - current ``queued`` / ``running`` counts\n  - ``failed_last_24h`` / ``completed_last_24h`` counts\n  - ``p50_duration_seconds`` / ``p95_duration_seconds`` over the\n    completed jobs in the last 24h (None when no samples)\n  - ``by_data_source`` — total job count per data_source bucket\n  - ``active_workers`` — list of distinct worker_ids currently\n    attached to a running job","operationId":"get_jobs_stats_admin_jobs_stats_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Jobs Stats Admin Jobs Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}":{"get":{"tags":["Admin"],"summary":"Get Job Detail","operationId":"get_job_detail_admin_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"event_limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Event Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Job Detail Admin Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}/events":{"get":{"tags":["Admin"],"summary":"Get Job Events","operationId":"get_job_events_admin_jobs__job_id__events_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"created_at ISO of last seen event","title":"Cursor"},"description":"created_at ISO of last seen event"},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Job Events Admin Jobs  Job Id  Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}/cancel":{"post":{"tags":["Admin"],"summary":"Cancel Job","description":"Request cancellation by setting ``cancel_requested=True``.\n\nReturns 204 once the flag is set (or was already set on a non-\nterminal job). Returns 409 when the job is already in a terminal\nstate — there's nothing to cancel.","operationId":"cancel_job_admin_jobs__job_id__cancel_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/jobs/{job_id}/requeue":{"post":{"tags":["Admin"],"summary":"Requeue Job","description":"Reset a failed job so a worker picks it up again.\n\nOnly valid for ``status='failed'`` rows. Resets:\n  - status         → queued\n  - attempt_count  → 0\n  - error_message  → null\n  - stage          → 'Requeued by admin'\n  - queued_at      → now\n  - started_at     → null\n  - completed_at   → null\n  - apify_run_id        → null  (so a fresh run kicks off)\n  - apify_dataset_id    → null\n  - apify_webhook_token → null\n  - cancel_requested    → False\n\nOther fields (priority, worker_id, etc.) are left alone — the\nscheduler claim cycle will overwrite them naturally.","operationId":"requeue_job_admin_jobs__job_id__requeue_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Requeue Job Admin Jobs  Job Id  Requeue Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/inbox":{"get":{"tags":["Admin"],"summary":"List Inbox","operationId":"list_inbox_admin_inbox_get","parameters":[{"name":"processed","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"true → only processed; false → only unprocessed; omitted → all","title":"Processed"},"description":"true → only processed; false → only unprocessed; omitted → all"},{"name":"signature_valid","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Signature Valid"}},{"name":"apify_run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Apify Run Id"}},{"name":"received_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp lower bound on received_at","title":"Received After"},"description":"ISO timestamp lower bound on received_at"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"received_at ISO of last seen row (paginate older)","title":"Cursor"},"description":"received_at ISO of last seen row (paginate older)"},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Inbox Admin Inbox Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/inbox/{inbox_id}":{"get":{"tags":["Admin"],"summary":"Get Inbox Detail","operationId":"get_inbox_detail_admin_inbox__inbox_id__get","parameters":[{"name":"inbox_id","in":"path","required":true,"schema":{"type":"string","title":"Inbox Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Inbox Detail Admin Inbox  Inbox Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/inbox/{inbox_id}/reprocess":{"post":{"tags":["Admin"],"summary":"Reprocess Inbox","description":"Reset the processing counters on an inbox row so the next\n``inbox_processor`` tick picks it up again.\n\nUse after fixing whatever made it fail (DB schema mismatch, third-\nparty crawl error, etc.). Idempotent — calling twice still leaves\nthe row at zero attempts.","operationId":"reprocess_inbox_admin_inbox__inbox_id__reprocess_post","parameters":[{"name":"inbox_id","in":"path","required":true,"schema":{"type":"string","title":"Inbox Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Reprocess Inbox Admin Inbox  Inbox Id  Reprocess Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel":{"get":{"tags":["Admin"],"summary":"List User Intel","description":"Leaderboard of all workspaces with their last cached verdict summary.\n\nWrapped in a catch-all that LOGS the full traceback and re-raises as a\n500 carrying the exception type + message (issue #524). FastAPI's default\nunhandled-exception path returns a bodyless ``Internal Server Error`` —\nwhich the BFF turns into an opaque ``{}`` — so an operator had no way to\nsee *why* the dashboard failed. Surfacing the detail makes the next\nfailure self-diagnosing; ``build_user_index`` separately skips individual\nbad workspaces so one row can't take the whole page down.","operationId":"list_user_intel_admin_user_intel_get","parameters":[{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"spend|paid|recency|searches|emails|signups|pay_likelihood","default":"recency","title":"Sort"},"description":"spend|paid|recency|searches|emails|signups|pay_likelihood"},{"name":"order","in":"query","required":false,"schema":{"enum":["asc","desc"],"type":"string","default":"desc","title":"Order"}},{"name":"paid","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter to paid (true) or free (false) workspaces","title":"Paid"},"description":"Filter to paid (true) or free (false) workspaces"},{"name":"activity","in":"query","required":false,"schema":{"anyOf":[{"enum":["active","dormant"],"type":"string"},{"type":"null"}],"description":"active = used in last 14d; dormant = no activity 14d+ / never","title":"Activity"},"description":"active = used in last 14d; dormant = no activity 14d+ / never"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive email / workspace-name contains","title":"Q"},"description":"Case-insensitive email / workspace-name contains"},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List User Intel Admin User Intel Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/job/{job_id}/results":{"get":{"tags":["Admin"],"summary":"Get Job Results","description":"The actual KOLs + stats + contacts a user received from one search —\nso an operator can verify the data is correct/relevant, not just trust\nthe count. Powers the god-view search drill-down. Read-only.","operationId":"get_job_results_admin_user_intel_job__job_id__results_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":30,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Job Results Admin User Intel Job  Job Id  Results Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/hot-leads":{"get":{"tags":["Admin"],"summary":"Get Hot Leads","description":"High-intent, not-yet-paying workspaces ranked by lead score — the few\nusers worth a human reach-out before they churn. Score + reasons are\ncheap heuristics (no LLM); click through to ``analyze`` for the verdict.","operationId":"get_hot_leads_admin_user_intel_hot_leads_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"min_score","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":0,"default":1,"title":"Min Score"}},{"name":"include_paid","in":"query","required":false,"schema":{"type":"boolean","description":"Include already-paying workspaces","default":false,"title":"Include Paid"},"description":"Include already-paying workspaces"},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Hot Leads Admin User Intel Hot Leads Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/hot-leads/digest":{"post":{"tags":["Admin"],"summary":"Post Hot Leads Digest","description":"Push the top hot leads to the admin Slack channel. Idempotent + safe to\ncall on a schedule: returns ``{sent:false}`` cleanly when there are no\nqualifying leads or the Slack webhook isn't configured. Intended to be hit\ndaily by a Fly Scheduled Machine, same as the other admin cron endpoints.","operationId":"post_hot_leads_digest_admin_user_intel_hot_leads_digest_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":10,"title":"Limit"}},{"name":"min_score","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":0,"default":40,"title":"Min Score"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Post Hot Leads Digest Admin User Intel Hot Leads Digest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/funnel":{"get":{"tags":["Admin"],"summary":"Get Funnel","description":"Visitor→signup funnel + top bounce pages from PostHog (last N days).\nReturns ``{configured: false}`` when the PostHog query API isn't set up.","operationId":"get_funnel_admin_user_intel_funnel_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":30,"title":"Days"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Funnel Admin User Intel Funnel Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/{workspace_id}":{"get":{"tags":["Admin"],"summary":"Get User Intel","description":"Full per-workspace dossier + the last cached LLM report (or null).","operationId":"get_user_intel_admin_user_intel__workspace_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Intel Admin User Intel  Workspace Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/{workspace_id}/timeline":{"get":{"tags":["Admin"],"summary":"Get User Timeline","description":"Per-user behaviour timeline, newest first.\n\nMerges two sources: our own first-party ``user_events`` log (always\navailable — durable in Postgres) and, when configured, the workspace\nowner's recent PostHog events (page views + actions). The first-party\nevents mean the timeline is no longer empty on environments where the\nPostHog query API isn't set up.","operationId":"get_user_timeline_admin_user_intel__workspace_id__timeline_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get User Timeline Admin User Intel  Workspace Id  Timeline Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/{workspace_id}/workspace-view":{"get":{"tags":["Admin"],"summary":"Get Workspace View","description":"Read-only \"god view\" of a workspace's actual content — recent\nsearches, lists, watchlist, CRM conversations, tracked videos, discovery\nplans. Lets an operator see what's in a user's workspace (and where\nthey're stuck) without logging in as them. Strictly read-only.","operationId":"get_workspace_view_admin_user_intel__workspace_id__workspace_view_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Workspace View Admin User Intel  Workspace Id  Workspace View Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/user-intel/{workspace_id}/analyze":{"post":{"tags":["Admin"],"summary":"Analyze User Intel","description":"Build the dossier, run the LLM, persist + return the report.\n\nOn-demand + cached: when a cached report already exists and\n``refresh`` is false, it is returned verbatim (``cached=true``)\nwithout another LLM call. ``refresh=true`` always re-runs and\noverwrites the cached row.","operationId":"analyze_user_intel_admin_user_intel__workspace_id__analyze_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force re-analysis even if a cached report exists","default":false,"title":"Refresh"},"description":"Force re-analysis even if a cached report exists"},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Analyze User Intel Admin User Intel  Workspace Id  Analyze Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/collections":{"post":{"tags":["admin"],"summary":"Upsert Collection","description":"Create (or update, keyed on ``public_slug``) a featured collection.","operationId":"upsert_collection_admin_collections_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCollectionRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Upsert Collection Admin Collections Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/collections/{list_id}/members":{"post":{"tags":["admin"],"summary":"Add Collection Members","description":"Bulk-add curated creators by username. Skips unknown / duplicate handles;\ncomputes ``tier`` + price the same way ordinary list members do.","operationId":"add_collection_members_admin_collections__list_id__members_post","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/api__routes__admin_collections__AddMembersRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Add Collection Members Admin Collections  List Id  Members Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recurring-jobs":{"get":{"tags":["admin"],"summary":"List Recurring Jobs","description":"Every discovery plan (all users) + run/cost/result stats, a spend\nsummary + guardrail status, and the read-only background-scheduler list.","operationId":"list_recurring_jobs_admin_recurring_jobs_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Recurring Jobs Admin Recurring Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recurring-jobs/{plan_id}":{"patch":{"tags":["admin"],"summary":"Control Plan","description":"Pause/resume (status), change cadence (interval_hours, floored by the\nguardrail), or change volume (max_videos_per_run).","operationId":"control_plan_admin_recurring_jobs__plan_id__patch","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanControl"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Control Plan Admin Recurring Jobs  Plan Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recurring-jobs/{plan_id}/run-now":{"post":{"tags":["admin"],"summary":"Run Plan Now","description":"Make the plan due immediately; the discovery scheduler spawns it on the\nnext tick (≤ discovery_tick_seconds). Activates a paused plan.","operationId":"run_plan_now_admin_recurring_jobs__plan_id__run_now_post","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Run Plan Now Admin Recurring Jobs  Plan Id  Run Now Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/unsubscribe/{conversation_id}":{"post":{"tags":["Unsubscribe"],"summary":"Unsubscribe One Click","description":"RFC 8058 one-click POST target. Mail clients call this with an\nempty body when the user hits Gmail's / Outlook's Unsubscribe button.\nMust return 2xx to count as success on the client side.","operationId":"unsubscribe_one_click_api_unsubscribe__conversation_id__post","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Unsubscribe One Click Api Unsubscribe  Conversation Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Unsubscribe"],"summary":"Unsubscribe Via Link","description":"Friendly page served when the user clicks the unsubscribe link\ndirectly (not the one-click button). Same effect, just with a\nthank-you page so the user sees confirmation.","operationId":"unsubscribe_via_link_api_unsubscribe__conversation_id__get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/enrich/search":{"post":{"tags":["Enrichment"],"summary":"Ic Search","description":"Search TikTok creators via Influencers Club Discovery API. Costs 0.01 credits/result.\nResults are cached for 24 hours. Pass refresh=true to bypass cache.","operationId":"ic_search_api_enrich_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ICSearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/enrich/tiktok":{"post":{"tags":["Enrichment"],"summary":"Enrich Tiktok","description":"Full TikTok profile enrichment — email, analytics, audience data. Costs 1 credit.","operationId":"enrich_tiktok_api_enrich_tiktok_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/enrich/tiktok/raw":{"post":{"tags":["Enrichment"],"summary":"Enrich Tiktok Raw","description":"Basic TikTok platform data. Costs 0.03 credits.","operationId":"enrich_tiktok_raw_api_enrich_tiktok_raw_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichRawRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/enrich/email":{"post":{"tags":["Enrichment"],"summary":"Enrich By Email","description":"Find creator by email address. Costs 0.05 credits.","operationId":"enrich_by_email_api_enrich_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichEmailRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/enrich/socials":{"post":{"tags":["Enrichment"],"summary":"Enrich Socials","description":"Find cross-platform social accounts for a TikTok user. Costs 0.5 credits.","operationId":"enrich_socials_api_enrich_socials_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichSocialsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/enrich/posts":{"post":{"tags":["Enrichment"],"summary":"Enrich Posts","description":"Get recent TikTok posts with engagement metrics. Costs 0.03 credits.","operationId":"enrich_posts_api_enrich_posts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichPostsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/enrich/credits":{"get":{"tags":["Enrichment"],"summary":"Enrich Credits","description":"Check remaining Influencers Club API credits.","operationId":"enrich_credits_api_enrich_credits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/modash/search":{"post":{"tags":["Modash"],"summary":"Modash Search","description":"Search TikTok influencers using Modash Discovery API filters. Cached for 24h.","operationId":"modash_search_api_modash_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModashSearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModashSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/modash/ai-search":{"post":{"tags":["Modash"],"summary":"Modash Ai Search","description":"AI-powered text-based TikTok influencer search via Modash. Cached for 24h.","operationId":"modash_ai_search_api_modash_ai_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModashAISearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModashSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/modash/profile/{user_id}":{"get":{"tags":["Modash"],"summary":"Modash Profile","description":"Get a full Modash profile report for a TikTok user. Costs 1 credit.","operationId":"modash_profile_api_modash_profile__user_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modash/credits":{"get":{"tags":["Modash"],"summary":"Modash Credits","description":"Check remaining Modash API credits and rate limits.","operationId":"modash_credits_api_modash_credits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModashCreditsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/lists/{list_id}/monitoring":{"post":{"tags":["Monitoring"],"summary":"Toggle List Monitoring","description":"Enable or disable daily monitoring for a KOL list.\n\nWhen enabled, all KOLs in this list will have daily snapshots created\nautomatically at 2 AM UTC via Celery beat scheduler.","operationId":"toggle_list_monitoring_api_lists__list_id__monitoring_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnableMonitoringRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitoringStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Monitoring"],"summary":"Get List Monitoring Status","description":"Get monitoring status for a KOL list.","operationId":"get_list_monitoring_status_api_lists__list_id__monitoring_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitoringStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/snapshot":{"post":{"tags":["Monitoring"],"summary":"Create Manual Snapshot","description":"Manually create a snapshot for a KOL.\n\nThis is useful for testing or creating snapshots on-demand\noutside of the daily automated schedule.","operationId":"create_manual_snapshot_api_kols__username__snapshot_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/trends":{"get":{"tags":["Monitoring"],"summary":"Get Kol Growth Trends","description":"Get growth trends for a KOL over the specified period.\n\nArgs:\n    username: KOL username\n    days: Number of days to analyze (30, 90, or 365)\n\nReturns:\n    Growth metrics including follower growth rate, views growth, and ER change","operationId":"get_kol_growth_trends_api_kols__username__trends_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrowthTrendsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/instagram/scrape":{"post":{"tags":["Instagram"],"summary":"Submit Instagram Scrape","description":"Submit an Instagram scrape job.\n\nPre-charges credits using the same ``ceil(max_videos / N)`` shape as\nthe TikTok route. The IG actor is comparably priced (≈ $2.50 / 1K\nprofiles vs ≈ $1.70 / 1K TikTok videos) but with hashtag fan-out\nwe typically touch many profiles per call — so the cost driver is\nprofile count, not raw max_videos. Phase 1 uses a flat\n``ceil(max_videos / 10)`` to match the TikTok pre-charge until the\ncross-cutting pricing-per-platform sub-issue lands.","operationId":"submit_instagram_scrape_api_instagram_scrape_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstagramScrapeRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstagramScrapeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/apify":{"post":{"tags":["Webhooks"],"summary":"Apify Webhook","description":"Persist the raw Apify webhook payload after verifying the\nper-task token. Always returns 200 (even on duplicate / bad\ntoken / unparseable body) so Apify doesn't keep retrying — the\ninbox row carries enough state for the processor / admin UI\nto act on.","operationId":"apify_webhook_webhooks_apify_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Apify Webhook Webhooks Apify Post"}}}}}}},"/webhooks/stripe":{"post":{"tags":["Webhooks"],"summary":"Stripe Lifecycle Webhook","description":"Fan out a Stripe lifecycle event to Slack + Loops.so.\n\nCalled by ``kolens-web/app/api/stripe/webhook/route.ts`` AFTER the\nStripe signature has been verified on Vercel. Auth is the shared\n``X-Admin-Secret`` (same pattern as ``/api/workspaces/.../billing/\ncredit-grant``). Returns 200 with ``{received, processed,\nslack_sent, loops_sent}`` so the BFF can log what fired without\nneeding to wait on either downstream provider.\n\nEvent types handled:\n\n* ``checkout.session.completed`` →\n  Slack: ``:tada: New paying customer: <email> (<plan>, <amount>)``\n  Loops: ``subscription_purchased``\n* ``customer.subscription.deleted`` →\n  Slack: ``:disappointed: Churned customer: <email>``\n  Loops: ``subscription_canceled``\n* ``invoice.payment_failed`` →\n  Slack: ``:warning: Payment failed: <email>``\n  Loops: ``payment_failed``\n\nAny other event type returns 200 with ``processed=False`` —\nunknown types are not an error. All integrations gracefully no-op\nwhen their env keys are unset; the response reflects what actually\nfired.","operationId":"stripe_lifecycle_webhook_webhooks_stripe_post","parameters":[{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeLifecycleEvent"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Stripe Lifecycle Webhook Webhooks Stripe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/douyin/scrape":{"post":{"tags":["Douyin"],"summary":"Submit Douyin Scrape","description":"Submit a Douyin scrape job.\n\nPre-charges credits using the same ``ceil(max_results / 10)``\nshape as the TikTok / IG routes. The Douyin actors run a bit\npricier per row (~$4-7 / 1K) but the per-job spend stays\ncomparable since the keyword cap is 300 hard. The cross-cutting\npricing-per-platform sub-issue tunes this later.","operationId":"submit_douyin_scrape_api_douyin_scrape_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DouyinScrapeRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DouyinScrapeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/x/scrape":{"post":{"tags":["X"],"summary":"Submit X Scrape","description":"Submit an X (Twitter) scrape job.\n\nPre-charges credits using the same ``ceil(max_tweets / 10)`` shape\nas the YT / IG routes. The apidojo X actor is cheaper per item\n(~$0.40 / 1K tweets vs ~$2.40 / 1K YT videos) so this is a\nconservative over-estimate until the cross-cutting\npricing-per-platform sub-issue lands.","operationId":"submit_x_scrape_api_x_scrape_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/XScrapeRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/XScrapeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search-history":{"post":{"tags":["Search History"],"summary":"Create Search History","description":"Save a search history record.","operationId":"create_search_history_api_search_history_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchHistoryCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Search History"],"summary":"List Search History","description":"List recent search history records.","operationId":"list_search_history_api_search_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery-plans":{"post":{"tags":["Discovery"],"summary":"Create Plan","operationId":"create_plan_api_discovery_plans_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoveryPlanCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoveryPlanOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Discovery"],"summary":"List Plans","description":"List discovery plans owned by the calling user.\n\nScoping (mirrors /api/jobs):\n  - With X-Kolens-User-Id (the normal web path): restrict to plans\n    owned by that user. Both DiscoveryPlan.user_id and\n    APIKey.user_id are honoured (older rows where user_id is NULL\n    but the api_key resolves to a user still belong to them).\n  - Without the header (CLI / Cowork plugin): legacy per-api_key.\n\nWas previously scoped only by api_key. Every web user shares the\nsame KOLENS_API_KEY through the Vercel proxy, so plans LEAKED\nacross users on the listing page until this fix.","operationId":"list_plans_api_discovery_plans_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["draft","active","disabled"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DiscoveryPlanOut"},"title":"Response List Plans Api Discovery Plans Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery-plans/{plan_id}":{"get":{"tags":["Discovery"],"summary":"Get Plan","operationId":"get_plan_api_discovery_plans__plan_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoveryPlanOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Discovery"],"summary":"Update Plan","operationId":"update_plan_api_discovery_plans__plan_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoveryPlanUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoveryPlanOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Discovery"],"summary":"Delete Plan","operationId":"delete_plan_api_discovery_plans__plan_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery-plans/{plan_id}/run-now":{"post":{"tags":["Discovery"],"summary":"Run Plan Now","description":"Spawn a scrape job from this plan right now. Does NOT advance next_run_at.\n\nCredits are pre-charged inside ``spawn_job_from_plan`` (since the\ndiscovery-charge fix). On insufficient balance we surface a 402\nwith the exact gap so the UI can render a precise message.","operationId":"run_plan_now_api_discovery_plans__plan_id__run_now_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoveryRunNowResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery-plans/{plan_id}/results":{"get":{"tags":["Discovery"],"summary":"Get Plan Results","operationId":"get_plan_results_api_discovery_plans__plan_id__results_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Plan Results Api Discovery Plans  Plan Id  Results Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery-plans/{plan_id}/videos":{"get":{"tags":["Discovery"],"summary":"Get Plan Videos","operationId":"get_plan_videos_api_discovery_plans__plan_id__videos_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"views","title":"Sort"}},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"},"title":"Response Get Plan Videos Api Discovery Plans  Plan Id  Videos Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts":{"get":{"tags":["Alerts"],"summary":"List Alerts","operationId":"list_alerts_api_alerts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"enum":["rising_kol","trending_video","audience_drift","comment_keyword"],"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"unread","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unread"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrowthAlertList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts/{alert_id}/read":{"post":{"tags":["Alerts"],"summary":"Mark Alert Read","operationId":"mark_alert_read_api_alerts__alert_id__read_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkReadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts/bulk-read":{"post":{"tags":["Alerts"],"summary":"Bulk Mark Read","description":"Mark several alerts read/unread in one call.\n\nPowers multi-select on the /alerts feed. Scoped exactly like the\nsingle-row route — the caller can only flip alerts they're allowed\nto see, so an attacker passing someone else's id just no-ops (it\nwon't match the WHERE clause). Returns how many rows actually\nchanged owner-side so the UI can reconcile its optimistic update.","operationId":"bulk_mark_read_api_alerts_bulk_read_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReadRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alerts/detect-now":{"post":{"tags":["Alerts"],"summary":"Detect Now","description":"Run the growth detector synchronously for the calling api_key.","operationId":"detect_now_api_alerts_detect_now_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetectNowResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/alert-preferences":{"get":{"tags":["Alerts"],"summary":"Get Alert Preferences","operationId":"get_alert_preferences_api_alert_preferences_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertPreferenceOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Alerts"],"summary":"Upsert Alert Preferences","operationId":"upsert_alert_preferences_api_alert_preferences_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertPreferenceIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertPreferenceOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/alert-preferences/test":{"post":{"tags":["Alerts"],"summary":"Test Alert Preferences","description":"Fire a synthetic alert through every enabled channel.\n\nReturns the per-channel success/error map so the settings UI can\nshow the user what worked and what didn't. The synthetic alert is\nNOT persisted — this is a dry-run probe, not a real signal.\n\n``channel`` lets the settings UI fire ONE channel at a time —\nclicking \"Test email\" should not also POST to the user's webhook\n(which would noise up their Slack channel for no reason). Implemented\nby temporarily disabling the other channel on the in-memory prefs\nrow before calling ``deliver_alert`` and restoring afterwards, so\nthe dispatcher logic stays untouched and the saved row is unchanged.\n\nUX: when no preferences row exists yet, we upsert one from the\nbody (treating Test as Save+Test). This lets the user paste a\nwebhook URL and immediately click Test without first hitting\nSave — the previous \"save preferences first?\" 404 was a confusing\ndead end.","operationId":"test_alert_preferences_api_alert_preferences_test_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(email|webhook)$"},{"type":"null"}],"description":"Restrict the probe to a single channel. Omit to test all enabled channels.","title":"Channel"},"description":"Restrict the probe to a single channel. Omit to test all enabled channels."},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertPreferenceIn","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Test Alert Preferences Api Alert Preferences Test Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/alerts/mark-unreliable-read":{"post":{"tags":["Admin"],"summary":"Mark Unreliable Rising Read","description":"One-time, idempotent, NON-DESTRUCTIVE sweep over historical alerts.\n\nThe pre-#666 growth detector fired a fresh ``rising_kol`` alert daily\noff an unreliable avg-views sampling artifact (e.g. @barstoolsports\n228K→3.7M ≈ 16×). #666/#667 added a reliability gate that HIDES those\nrows from the feed and the watchlist badge, but the rows are still\n``is_read = False`` on disk, so the DB-wide unread count stays inflated\n(an empty feed but \"13 unread\"). The post-#666 detector no longer\nfires these, so the backlog is finite — this admin op marks the\nhistorical artifacts read so the unread count matches the feed.\n\nSelection + gate (identical to what the feed/watchlist filter on):\n  * Scope: ``alert_type == \"rising_kol\"`` AND ``is_read == False``\n    across ALL workspaces/users (admin maintenance op).\n  * For each row, re-evaluate the SAME canonical gate the feed uses —\n    ``watchlist_signals._rising_alert_is_reliable(payload)`` →\n    ``views_reliability.is_views_delta_unreliable`` on the payload's\n    ``avg_views_before`` / ``avg_views_after`` / ``follower_after``.\n    Missing keys / legacy shapes ⇒ treated reliable (NOT marked).\n  * Mark ``is_read = True`` ONLY for rows that are NOT reliable, i.e.\n    provably-unreliable artifacts the feed already hides. We never\n    mark a row read that the feed would still SHOW.\n\nGuarantees: never deletes anything; never touches other alert types,\nreliable risers, or already-read rows; no schema change. Re-running is\na no-op once the backlog is cleared (the second run scans the now-empty\nremaining unread set). ``dry_run=true`` commits nothing.","operationId":"mark_unreliable_rising_read_api_admin_alerts_mark_unreliable_read_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkUnreliableReadRequest","default":{"dry_run":false}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkUnreliableReadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist":{"get":{"tags":["Watchlist"],"summary":"List Watchlist","description":"Return the user's watchlist as a single significance-ranked change\ndigest.\n\nOne call yields everything the watchlist UI needs: per-entry identity,\ncurrent stats, 7d/30d snapshot deltas, recent activity events, the\n\"what changed\" signal badges, a cached LLM activity summary if present,\nand a 0-100 significance score + tier. ``entries`` is sorted by\n``significance.score`` descending — the most-changed creator first.\n\nNo ``?signals=`` flag any more: the digest IS the response. The\nfrontend must not fan out to ``GET /api/kols/{username}`` per row.","operationId":"list_watchlist_api_watchlist_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchlistDigestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Watchlist"],"summary":"Add Watchlist","description":"Idempotent: re-adding an existing (user, username, platform) returns the existing row.\n\n``platform`` defaults to ``\"tiktok\"`` so legacy callers (web Star\nbutton, plugin) keep working unchanged. The same handle on a\ndifferent platform is a separate row — the unique index is\n(user_id, username, platform).","operationId":"add_watchlist_api_watchlist_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}},{"name":"x-kolens-locale","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Locale"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchlistAdd"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchlistEntryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/{username}":{"delete":{"tags":["Watchlist"],"summary":"Remove Watchlist","description":"Remove a (user, username, platform) row from the watchlist.\n\n``?platform=`` is a query parameter; default is ``\"tiktok\"`` so\nlegacy clients that pass no platform keep deleting the TikTok row.\nIdempotent — delete-of-nonexistent is a no-op (returns 204).","operationId":"remove_watchlist_api_watchlist__username__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","default":"tiktok","title":"Platform"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/{username}/audience":{"put":{"tags":["Watchlist"],"summary":"Update Watchlist Audience Prefs","description":"AP-4: per-row audience auto-refresh prefs.\n\nPATCH-style — only the fields present in the body are touched.\nSample size must be one of the priced tiers (200 / 500 / 2000) so\nthe scheduler always knows what to charge.","operationId":"update_watchlist_audience_prefs_api_watchlist__username__audience_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchlistAudiencePrefs"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchlistEntryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/track":{"post":{"tags":["Video Tracking"],"summary":"Track Video","description":"Start tracking a published TikTok video.\n\nIdempotent on ``(user, platform, video_id)``: re-tracking the same video\nreturns (and re-activates) the existing row. A best-effort first capture\nruns inline so the growth curve has a t0 point when Apify is configured;\non failure (or when ``APIFY_API_TOKEN`` is unset) the row is left\nimmediately due so the scheduler captures on its next tick.","operationId":"track_video_api_videos_track_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}},{"name":"x-kolens-locale","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Locale"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackVideoRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackedVideoOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/tracked":{"get":{"tags":["Video Tracking"],"summary":"List Tracked","description":"List tracked videos visible to the caller, partitioned own vs\ncompetitor, each with its latest snapshot. Frontends render two\ncolumns from this.\n\n``scope`` is what the Personal/Team switcher sends. It used to be\naccepted by the proxy and dropped here (FastAPI ignores unknown\nquery params), which is why the toggle appeared to do nothing on\nthis page.","operationId":"list_tracked_api_videos_tracked_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"label","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: own | competitor","title":"Label"},"description":"Filter: own | competitor"},{"name":"scope","in":"query","required":false,"schema":{"type":"string","description":"personal | team | both — which rows count as visible","default":"both","title":"Scope"},"description":"personal | team | both — which rows count as visible"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Tracked Api Videos Tracked Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/tracking":{"get":{"tags":["Video Tracking"],"summary":"Get Video Tracking","description":"Single-video detail: metric time-series + 7d/30d deltas + content.\nComments + audience arrive in P2/P3.","operationId":"get_video_tracking_api_videos__video_id__tracking_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Video Tracking Api Videos  Video Id  Tracking Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/refresh":{"post":{"tags":["Video Tracking"],"summary":"Refresh Video Metrics","description":"Re-capture a tracked video's metrics via Apify right now.\n\nThe on-demand counterpart to the scheduler tick — used by the \"retry\"\nbutton when a capture failed (e.g. a transient Apify error or a quota\nlimit that has since been raised). Best-effort: an Apify failure or unset\ntoken returns ``{\"ok\": False, \"error\": ...}`` instead of a 5xx, and the\nerror is stamped on the row so the UI can keep showing it. On success the\ncadence anchors advance and ``last_error`` is cleared.","operationId":"refresh_video_metrics_api_videos__video_id__refresh_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Video Metrics Api Videos  Video Id  Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/comments":{"get":{"tags":["Video Tracking"],"summary":"Get Video Comments","description":"Paginated comment stream (most-liked first) + sentiment summary for a\ntracked video. EPIC #514 Phase 2.","operationId":"get_video_comments_api_videos__video_id__comments_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Video Comments Api Videos  Video Id  Comments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/comments/refresh":{"post":{"tags":["Video Tracking"],"summary":"Refresh Video Comments","description":"Fetch the latest comments via Apify now and (re)classify sentiment.\n\nReturns the capture counts + the refreshed summary. Best-effort: an Apify\nfailure or unset token returns ``{\"ok\": False, \"error\": ...}`` rather than\na 5xx so the UI can show a soft \"couldn't refresh\" state.","operationId":"refresh_video_comments_api_videos__video_id__comments_refresh_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"max_comments","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Max Comments"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Refresh Video Comments Api Videos  Video Id  Comments Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/summary":{"post":{"tags":["Video Tracking"],"summary":"Generate Video Summary","description":"On-demand AI summary of the tracked video (content meaning, quality\nverdict, buyer-intent read). Cached on the row — repeat calls return the\ncached result unless ``refresh=true`` OR the cached summary was written\nin a different language than ``lang`` (so the summary matches the page\nlanguage).","operationId":"generate_video_summary_api_videos__video_id__summary_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force re-analysis even if cached","default":false,"title":"Refresh"},"description":"Force re-analysis even if cached"},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"UI locale — the summary prose is written in this language","default":"en","title":"Lang"},"description":"UI locale — the summary prose is written in this language"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Generate Video Summary Api Videos  Video Id  Summary Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/campaign-tag":{"put":{"tags":["Video Tracking"],"summary":"Set Video Campaign Tag","description":"Correct the campaign this video is grouped under.\n\nThe label is otherwise guessed from the post's caption + hashtags,\nwhich works until a video carries several. \"#fyp #goglowai #skincare\"\nhas no way of announcing which one is the campaign, and a wrong guess\ndoes not just mislabel one card — it moves that video's views into\nanother campaign's totals, so the number an operator reports to a\nclient is wrong. Hence a correction, and hence the correction wins\nover detection rather than merely competing with it.\n\nSending null or an empty string clears the override and restores\ndetection — that is how a bad correction gets undone.\n\nEditable by any workspace member who can see the video, unlike alert\nkeywords next door. The distinction is what the field does: keywords\nconfigure the OWNER'S notifications, while this is a shared\nbookkeeping fact feeding totals the whole team reads. A teammate who\ncan see a mislabelled video and cannot fix it just files a complaint\ninstead.","operationId":"set_video_campaign_tag_api_videos__video_id__campaign_tag_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignTagRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Video Campaign Tag Api Videos  Video Id  Campaign Tag Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/keywords":{"put":{"tags":["Video Tracking"],"summary":"Set Video Keywords","description":"Set the tracked video's custom comment-alert keywords. A new comment\nmatching any of these (or the built-in purchase-intent set) fires an\nalert through the user's alert channel. Normalised: lowercase, trimmed,\ndeduped, capped (50 keywords × 60 chars).\n\nOwner-only even when the row is shared with the team: these keywords\ndecide what the OWNER gets pinged about, and a teammate rewriting\nthem changes someone else's notifications without telling them.","operationId":"set_video_keywords_api_videos__video_id__keywords_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeywordsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Video Keywords Api Videos  Video Id  Keywords Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/{video_id}/track":{"delete":{"tags":["Video Tracking"],"summary":"Untrack Video","description":"Stop tracking a video (hard delete of the subscription row). The\ncaptured snapshots stay — they remain useful history and feed trending\ndetection.\n\nOwner-only: a shared row is still somebody's tracking, and deleting\nit would end their refreshes and their alerts. Removing a teammate's\nvideo from your own view is a different feature (un-sharing), not\nthis one.","operationId":"untrack_video_api_videos__video_id__track_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Untrack Video Api Videos  Video Id  Track Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/videos/compare":{"get":{"tags":["Video Tracking"],"summary":"Compare Videos","description":"Own-vs-competitor comparison: returns each requested video's detail\npayload (curve + latest + deltas) side by side.","operationId":"compare_videos_api_videos_compare_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated tracked video_ids","title":"Ids"},"description":"Comma-separated tracked video_ids"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Compare Videos Api Videos Compare Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaigns":{"post":{"tags":["Campaigns"],"summary":"Create Campaign","operationId":"create_campaign_api_campaigns_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Campaign Api Campaigns Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Campaigns"],"summary":"List Campaigns","description":"Every campaign in the workspace, each with its full summary.\n\nThe summary is included rather than a bare list: the list view's\nwhole job is to show spend against budget, and fetching that per\nrow would be N requests to render one page.","operationId":"list_campaigns_api_campaigns_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Campaigns Api Campaigns Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaigns/{campaign_id}":{"get":{"tags":["Campaigns"],"summary":"Get Campaign","operationId":"get_campaign_api_campaigns__campaign_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Get Campaign Api Campaigns  Campaign Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Campaigns"],"summary":"Update Campaign","operationId":"update_campaign_api_campaigns__campaign_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Update Campaign Api Campaigns  Campaign Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Campaigns"],"summary":"Delete Campaign","description":"Delete a campaign and its placements.\n\nAttached videos survive. Tracking a video and having paid for it are\ndifferent facts, and losing the tracking because the bookkeeping was\ntidied would be genuine data loss.\n\nThe detach is done here rather than left to the column's\n``ON DELETE SET NULL``. That clause describes the model, but\n``placement_id`` reaches the deployed table through\n``ALTER TABLE ... ADD COLUMN`` in ``run_alter_migrations``, which\nadds no constraint — so in production there is no foreign key to\nact on and the videos would be left pointing at placements that no\nlonger exist. Doing it explicitly is the only version that is true\non both the model and the live database.","operationId":"delete_campaign_api_campaigns__campaign_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaigns/{campaign_id}/placements":{"post":{"tags":["Campaigns"],"summary":"Create Placement","description":"Record a creator's paid slot in this campaign.\n\nA placement may be created with no amount and no video: a deal\nagreed but neither paid nor published is a real state, and the\noperator wants it on the board rather than in their head.","operationId":"create_placement_api_campaigns__campaign_id__placements_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Create Placement Api Campaigns  Campaign Id  Placements Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaigns/{campaign_id}/placements/{placement_id}":{"patch":{"tags":["Campaigns"],"summary":"Update Placement","operationId":"update_placement_api_campaigns__campaign_id__placements__placement_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"placement_id","in":"path","required":true,"schema":{"type":"string","title":"Placement Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Update Placement Api Campaigns  Campaign Id  Placements  Placement Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Campaigns"],"summary":"Delete Placement","operationId":"delete_placement_api_campaigns__campaign_id__placements__placement_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"placement_id","in":"path","required":true,"schema":{"type":"string","title":"Placement Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaigns/{campaign_id}/placements/{placement_id}/videos":{"post":{"tags":["Campaigns"],"summary":"Attach Video","description":"Attach one of the caller's tracked videos to this placement.\n\nThis is where two visibility rules meet. A tracked video is shared\nwith a workspace only when its owner opts in; a campaign is the\nteam's by construction. Attaching therefore stamps the video with\nthe campaign's workspace — otherwise a teammate would see the spend\nbut not the video it bought, and the page would be arithmetic they\ncould not check.\n\n``shared_with_workspace`` in the response reports whether that just\nhappened, so the client can warn BEFORE the click rather than\napologise after.","operationId":"attach_video_api_campaigns__campaign_id__placements__placement_id__videos_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"placement_id","in":"path","required":true,"schema":{"type":"string","title":"Placement Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachVideoIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Attach Video Api Campaigns  Campaign Id  Placements  Placement Id  Videos Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaigns/{campaign_id}/placements/{placement_id}/videos/{video_id}":{"delete":{"tags":["Campaigns"],"summary":"Detach Video","description":"Detach a video from its placement.\n\nTracking continues and the workspace stamp stays: detaching says\n\"this video was not what we bought\", not \"un-share it\". Reversing\nthe share is a separate decision with its own consequences.","operationId":"detach_video_api_campaigns__campaign_id__placements__placement_id__videos__video_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","title":"Campaign Id"}},{"name":"placement_id","in":"path","required":true,"schema":{"type":"string","title":"Placement Id"}},{"name":"video_id","in":"path","required":true,"schema":{"type":"string","title":"Video Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Detach Video Api Campaigns  Campaign Id  Placements  Placement Id  Videos  Video Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/workspaces":{"get":{"tags":["Admin"],"summary":"List Workspaces","description":"Workspaces with their pool balance and member count.\n\nPersonal workspaces are included and flagged rather than hidden: a\nsolo customer's Personal workspace is exactly the one an operator\nneeds to find when comping a single user, and hiding it would send\nthem to the database instead.","operationId":"list_workspaces_admin_workspaces_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by name or slug","title":"Q"},"description":"Filter by name or slug"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Workspaces Admin Workspaces Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/workspaces/{slug}/credits":{"post":{"tags":["Admin"],"summary":"Grant Credits","description":"Set or adjust a workspace's credit balance by hand.\n\nNo payment is involved anywhere in this path — no Stripe call, no\ncheckout, no card. It writes ``workspaces.credits`` directly. The\nledger row it also writes lives in ``workspace_credit_purchases``\nonly because that is where a workspace's credit history is read\nfrom; its ``stripe_session_id`` column is a historical name being\nreused as a unique idempotency key, not a payment reference.\n\nTwo modes, because \"top up by 500\" and \"make the balance 500\" are\ndifferent operations and guessing which one an operator meant is\nhow a balance ends up wrong:\n\n* ``add`` — ``credits`` is a delta. Negative corrects an over-grant.\n* ``set`` — ``credits`` is the balance to end up with. The delta is\n  derived, so the ledger and the audit log still record what\n  actually moved.","operationId":"grant_credits_admin_workspaces__slug__credits_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Grant Credits Admin Workspaces  Slug  Credits Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/daily":{"get":{"tags":["Usage"],"summary":"Daily Usage","description":"Per-day rollup of credits charged + jobs run for the calling user.","operationId":"daily_usage_api_usage_daily_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"How many trailing days to include","default":30,"title":"Days"},"description":"How many trailing days to include"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Daily Usage Api Usage Daily Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/jobs":{"get":{"tags":["Usage"],"summary":"Usage Jobs","description":"List the calling user's completed jobs with what each one cost.","operationId":"usage_jobs_api_usage_jobs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Usage Jobs Api Usage Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage/ledger":{"get":{"tags":["Usage"],"summary":"Usage Ledger","description":"Unified credit history — debits (scrapes) AND credits (top-ups).\n\nThis is the feed the credits page renders. Before this endpoint the\npage only ever showed debits, so a user could watch their balance go\ndown but never saw the matching ``+N credits`` when they bought a\npack. We merge:\n\n  1. Debits — completed jobs that actually charged credits\n     (``credits_charged`` > 0) from our Postgres ``search_jobs``.\n  2. Credits — personal top-ups from the Supabase ``credit_purchases``\n     table (via ``CreditsService.list_purchases``), the same rows the\n     Orders page shows.\n\nBoth are sorted newest-first and capped at ``limit``. If Supabase is\nunreachable / unconfigured (local dev, CI) the credit side is simply\nempty and the feed degrades to debits-only — never an error.","operationId":"usage_ledger_api_usage_ledger_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity":{"get":{"tags":["Activity"],"summary":"List Activity","description":"Return the workspace's activity feed, newest first.\n\n``unseen_count`` in the response is the *workspace-wide* unseen\ntotal — independent of the current filter set — so the sidebar\nbadge stays accurate even when the user is viewing a filtered slice.","operationId":"list_activity_api_activity_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only events created >= this ISO8601 timestamp","title":"Since"},"description":"Only events created >= this ISO8601 timestamp"},{"name":"kinds","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of event kinds to include","title":"Kinds"},"description":"CSV of event kinds to include"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"unseen_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unseen Only"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Activity Api Activity Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/mark-seen":{"post":{"tags":["Activity"],"summary":"Mark Seen","description":"Bulk mark events as seen.\n\nModes:\n  - ``event_ids``  — exact-match IDs scoped to this workspace.\n  - ``all_before`` — every workspace row created <= cutoff that's\n                     still unseen.\n\nReturns the count of rows actually flipped. Already-seen rows are\nNOT re-stamped — keeping the original seen_at preserves the audit\nstory.","operationId":"mark_seen_api_activity_mark_seen_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkSeenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Mark Seen Api Activity Mark Seen Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/unseen-count":{"get":{"tags":["Activity"],"summary":"Unseen Count","description":"Single integer used by the sidebar badge — polled every 30s\nwhile the tab is visible. Keep cheap (indexed COUNT).","operationId":"unseen_count_api_activity_unseen_count_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Unseen Count Api Activity Unseen Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/demo-search":{"get":{"tags":["public"],"summary":"Public Demo Search","description":"Up to 5 KOLs whose cached videos / search history / category\nmatch the keyword. No auth, no credits, no scrape — visitor-facing\nteaser only.\n\nEmpty results when nothing is cached for that keyword. The UI's\n\"Sign up to scrape this fresh\" CTA is the conversion event.\n\nSets ``X-Demo-Quota-Remaining`` header so the UI can show\n\"X demos left today, sign up for unlimited\".","operationId":"public_demo_search_api_public_demo_search_get","parameters":[{"name":"keyword","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"title":"Keyword"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/kols/sitemap":{"get":{"tags":["public"],"summary":"Public Kols Sitemap","description":"Sitemap rows for every KOL whose page is indexable (tier 1 or 2).\n\nTier 3 pages are deliberately absent: they serve, but they carry\n``noindex``, and advertising a URL we have asked Google to drop wastes\ncrawl budget on the cluster's weakest pages.","operationId":"public_kols_sitemap_api_public_kols_sitemap_get","parameters":[{"name":"platform","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":20},{"type":"null"}],"title":"Platform"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50000,"minimum":1,"default":50000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"tiers","in":"query","required":false,"schema":{"type":"boolean","description":"Also return the tier-1/2/3 split.","default":false,"title":"Tiers"},"description":"Also return the tier-1/2/3 split."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitemapResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/kols/directory":{"get":{"tags":["public"],"summary":"Public Kols Directory","description":"Rich creator cards for the public directory (#648). Same eligibility +\nfollower-desc ordering as the sitemap endpoint, but returns reach/quality\nsignals (followers, engagement, avg views, category, country, verified) so\nthe frontend can render proper KOL cards instead of just a handle. NEVER\nincludes contact/email — reuses the canonical public projection\n(``featured_collections.project_author``).\n\n``category`` (optional) keeps the niche landing pages on-topic: pass the\nniche's keywords and only creators in that topic come back, instead of the\nglobal follower leaders.","operationId":"public_kols_directory_api_public_kols_directory_get","parameters":[{"name":"platform","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":20},{"type":"null"}],"default":"tiktok","title":"Platform"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"description":"Comma-separated topic keywords; restricts to creators whose inferred category or bio matches any keyword (niche pages).","title":"Category"},"description":"Comma-separated topic keywords; restricts to creators whose inferred category or bio matches any keyword (niche pages)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/kols/directory/aggregated":{"get":{"tags":["public"],"summary":"Public Kols Aggregated Directory","description":"Deduped, multi-platform creator directory: ONE entry per person\n(grouped by shared handle across TikTok / Instagram / X / Douyin), each\ncarrying its platform list + summed reach. This is what lets a list show\na single unified creator instead of duplicate per-platform cards.","operationId":"public_kols_aggregated_directory_api_public_kols_directory_aggregated_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":24,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Category"}},{"name":"min_followers","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":100000,"title":"Min Followers"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregatedDirectoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/kols/{username}/platforms":{"get":{"tags":["public"],"summary":"Public Kol Platforms","description":"Unify every platform row that shares this handle into one creator —\ne.g. ``@mkbhd`` on TikTok + Instagram + X returned as a single creator\nwith combined reach. 404 if the handle isn't in the DB on any platform.","operationId":"public_kol_platforms_api_public_kols__username__platforms_get","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregatedCreatorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/kols/{username}/similar":{"get":{"tags":["public"],"summary":"Get Public Kol Similar","description":"Public Related-Profiles roster — no auth required.\n\nDrives the \"Related Profiles\" section on ``/k/{username}``. Returns\n``limit`` neighbour creators (default 8, max 20) selected from the\nsame platform within a follower-count band around the seed.\n\nReturns 404 with the same body as the dossier route when the seed\ndoesn't exist or isn't public-eligible, so neither case is probe-\nable. 429 on rate limit, sharing the bucket with the dossier route\n(same IP, same window) — the frontend usually fetches both on a\nsingle page load so they collectively count toward the same quota.\n\nRoute ordering note: ``/kols/{username}/similar`` and\n``/kols/{username}`` are different paths in FastAPI's router — the\ntrailing segment disambiguates. Declaration order doesn't matter\nfor correctness; we place ``/similar`` first purely for file\nreadability (most-specific path first).","operationId":"get_public_kol_similar_api_public_kols__username__similar_get","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"default":"tiktok","title":"Platform"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":8,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimilarKOLsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/kols/{username}":{"get":{"tags":["public"],"summary":"Get Public Kol Dossier","description":"Public KOL dossier — no auth required.\n\nReturns 404 when:\n  - the creator doesn't exist in our database, OR\n  - the creator's follower count is at or below\n    ``PUBLIC_FOLLOWER_THRESHOLD`` (100k by default).\n\nBoth cases share the same 404 response so we never leak \"this\ncreator exists but isn't public yet\" via response shape.","operationId":"get_public_kol_dossier_api_public_kols__username__get","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"default":"tiktok","title":"Platform"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDossierOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/collections":{"get":{"tags":["public"],"summary":"List Collections","description":"Featured collections, ordered by ``featured_rank``, each with a short\nmember preview. For landing embeds / indexes. Empty collections omitted.","operationId":"list_collections_api_public_collections_get","parameters":[{"name":"vertical","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Vertical"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSummaryOut"},"title":"Response List Collections Api Public Collections Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/verticals/{vertical}":{"get":{"tags":["public"],"summary":"Get Vertical","description":"Every featured collection for a vertical with members fully expanded.\n404 when the vertical has no non-empty collections (keeps thin pages out of\nthe index).","operationId":"get_vertical_api_public_verticals__vertical__get","parameters":[{"name":"vertical","in":"path","required":true,"schema":{"type":"string","title":"Vertical"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerticalOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/collections/{public_slug}":{"get":{"tags":["public"],"summary":"Get Collection","operationId":"get_collection_api_public_collections__public_slug__get","parameters":[{"name":"public_slug","in":"path","required":true,"schema":{"type":"string","title":"Public Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/rankings":{"get":{"tags":["public"],"summary":"List Boards","description":"Every board that actually has rows.\n\nDrives the index page and the sitemap, so we never emit a link to a\ncountry that would render empty and get indexed as a thin page.","operationId":"list_boards_api_public_rankings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BoardSummaryOut"},"type":"array","title":"Response List Boards Api Public Rankings Get"}}}}}}},"/api/public/rankings/{country}":{"get":{"tags":["public"],"summary":"Get Board","operationId":"get_board_api_public_rankings__country__get","parameters":[{"name":"country","in":"path","required":true,"schema":{"type":"string","title":"Country"}},{"name":"window","in":"query","required":false,"schema":{"type":"string","description":"Trailing window: 7d | 30d | 120d","default":"7d","title":"Window"},"description":"Trailing window: 7d | 30d | 120d"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankingBoardOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/board-requests":{"post":{"tags":["Public"],"summary":"Create Board Request","description":"Record a visitor's ask for a market's creator board.\n\nReturns what the assistant can say *this turn*: ``ready`` with a URL\nwhen the board already exists, ``queued`` when it is now wanted, or\n``unsupported`` when TikTok publishes no feed we can sample for that\nmarket — recorded either way, because the ask is the roadmap.","operationId":"create_board_request_api_public_board_requests_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardRequestIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/scheduler/health":{"get":{"tags":["Admin"],"summary":"Scheduler Health","description":"Return scheduler running flag + queue counts. Cheap, no admin gate\n— anyone with the API key can poll this for monitoring dashboards.","operationId":"scheduler_health_api_admin_scheduler_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Scheduler Health Api Admin Scheduler Health Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/scheduler/tick":{"post":{"tags":["Admin"],"summary":"Force Tick","description":"Run one synchronous discovery scheduler ``tick()`` and return its\nsummary. Bypasses the normal 60s sleep so an operator can verify the\nloop body end-to-end without waiting.\n\nRefuses with 409 when ``SCRAPER_SCHEDULER_ENABLED=false`` on this\nreplica — the loop is disabled, so a manual tick would step on the\nsingle-owner invariant.\n\nRequires the ``X-Admin-Secret`` header (see api/dependencies.py).","operationId":"force_tick_api_admin_scheduler_tick_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Force Tick Api Admin Scheduler Tick Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/scheduler/restart":{"post":{"tags":["Admin"],"summary":"Restart Scheduler","description":"Tear down + restart the in-process scheduler. Use when health\nshows ``is_running=false`` or queued jobs aren't being picked up.\n\nRequires the X-Admin-Secret header (see api/dependencies.py).","operationId":"restart_scheduler_api_admin_scheduler_restart_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Restart Scheduler Api Admin Scheduler Restart Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/apify-webhook/health":{"get":{"tags":["Admin"],"summary":"Apify Webhook Health","description":"Aggregate the ``apify_webhook_inbox`` log into a delivery-health\nview so an operator can confirm webhooks are landing + being\nprocessed without inspecting each run in Apify's dashboard.\n\nRequires the ``X-Admin-Secret`` header.","operationId":"apify_webhook_health_api_admin_apify_webhook_health_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Apify Webhook Health Api Admin Apify Webhook Health Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/crm-scheduler/health":{"get":{"tags":["Admin"],"summary":"Crm Scheduler Health","description":"Return CRM scheduler running flag + queue counts.\n\nOutput shape:\n  {\n    \"is_running\": bool,\n    \"last_tick_at\": ISO8601 | null,\n    \"queue\": {\n      \"pending\":  int,   # status='pending' (any next_run_at)\n      \"due_now\":  int,   # status='pending' AND next_run_at <= now\n      \"failed\":   int,   # status='failed' — needs manual intervention\n    },\n    \"now\": ISO8601\n  }","operationId":"crm_scheduler_health_api_admin_crm_scheduler_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Crm Scheduler Health Api Admin Crm Scheduler Health Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/admin/lifecycle/churn/sweep":{"post":{"tags":["Admin","Customer Lifecycle"],"summary":"Manual Churn Sweep","description":"Run one pass of the churn detector synchronously.\n\nIdentical code path as the nightly scheduler — useful to verify\nintegrations + tune thresholds without waiting for the next tick.\nReturns the same per-sweep summary the scheduler logs.","operationId":"manual_churn_sweep_admin_lifecycle_churn_sweep_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Manual Churn Sweep Admin Lifecycle Churn Sweep Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/lifecycle/health":{"get":{"tags":["Admin","Customer Lifecycle"],"summary":"Lifecycle Health","description":"Report which lifecycle integrations are configured.\n\nRead-only diagnostic. Does not call any external service; just\ninspects whether the corresponding env vars are non-empty. Useful\nfor the \"is our prod setup complete?\" admin dashboard.","operationId":"lifecycle_health_admin_lifecycle_health_get","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Lifecycle Health Admin Lifecycle Health Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scheduled/daily-snapshot":{"post":{"tags":["Admin","Scheduled Tasks"],"summary":"Trigger Daily Snapshot","description":"Fire the daily KOL snapshot task (Fly Scheduled Machine primary trigger).\n\nReturns immediately; the actual work (iterating monitored lists,\ncreating ``KOLSnapshot`` rows) runs as a ``BackgroundTask`` so a\nslow sweep can't time out the caller's fetch.","operationId":"trigger_daily_snapshot_admin_scheduled_daily_snapshot_post","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"external","title":"Source"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Trigger Daily Snapshot Admin Scheduled Daily Snapshot Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scheduled/daily-activity-summary":{"post":{"tags":["Admin","Scheduled Tasks"],"summary":"Trigger Daily Activity Summary","description":"Fire the daily watchlist activity-summary refresh.","operationId":"trigger_daily_activity_summary_admin_scheduled_daily_activity_summary_post","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"external","title":"Source"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Trigger Daily Activity Summary Admin Scheduled Daily Activity Summary Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scheduled/refresh-stale-creators":{"post":{"tags":["Admin","Scheduled Tasks"],"summary":"Trigger Refresh Stale Creators","description":"Fire the daily recovery sweep — re-scrape TikTok creators whose\nmedia-cache jobs have exhausted MAX_ATTEMPTS (raw URLs already\nexpired, can't be fetched by ``avatar_cache`` / ``thumbnail_cache``).\nSee ``api/services/refresh_stale_creators.py`` for the picker and\ncost rationale; the recommended Fly schedule is daily 04:00 UTC.","operationId":"trigger_refresh_stale_creators_admin_scheduled_refresh_stale_creators_post","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"external","title":"Source"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Trigger Refresh Stale Creators Admin Scheduled Refresh Stale Creators Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scheduled/refresh-country-rankings":{"post":{"tags":["Admin","Scheduled Tasks"],"summary":"Trigger Refresh Country Rankings","description":"Fire the daily country-board sweep behind the public ``/rankings``\npages — rebuilds the stalest few markets, never-built ones first.\n\nDistinct from ``POST /admin/rankings/refresh``, which rebuilds\nexactly what an operator names and is the right tool for \"rebuild TW\nright now\". This one is the unattended schedule that stops the\nboards going stale in the first place; see\n``api/services/rankings_sweep.py`` for the picker and budget, and\n``docs/scheduler.md`` for where it sits. Recommended Fly schedule is\ndaily 05:00 UTC.","operationId":"trigger_refresh_country_rankings_admin_scheduled_refresh_country_rankings_post","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"external","title":"Source"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Trigger Refresh Country Rankings Admin Scheduled Refresh Country Rankings Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scheduled/runs":{"get":{"tags":["Admin","Scheduled Tasks"],"summary":"List Recent Runs","description":"Operator dashboard support — most-recent run records.\n\nReturns the ``scheduled_task_runs`` rows newest-first. Optional\n``task`` query param scopes to a single task name. When\n``include_aggregates=true`` the response also embeds a 30-day\nspend rollup the admin UI uses to render the \"30-day spend\" tile —\nsumming ``summary.credits_spent`` across every task whose summary\ncarries that key. Computed in-process; the row count is bounded\nby ~30 days × ~5 daily tasks so a SELECT scan is fine.","operationId":"list_recent_runs_admin_scheduled_runs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":20,"title":"Limit"}},{"name":"task","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task"}},{"name":"include_aggregates","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Aggregates"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List Recent Runs Admin Scheduled Runs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/ops/dingtalk-send":{"post":{"tags":["Admin"],"summary":"Send Dingtalk","description":"Send a DingTalk markdown message to the admin's configured webhook.","operationId":"send_dingtalk_admin_ops_dingtalk_send_post","parameters":[{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DingTalkSendIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DingTalkSendOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces":{"post":{"tags":["Workspaces"],"summary":"Create","operationId":"create_api_workspaces_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkspaceRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Workspaces"],"summary":"List Mine","description":"List the caller's workspaces.\n\nPhase 2 wiring (EPIC #114): on first call this lazily creates the\nuser's \"Personal\" workspace via ``ensure_personal_workspace``. We\nchose lazy-on-list rather than a global request middleware because:\n\n1. It only runs for endpoints that already resolve a user_id, so\n   OAuth flows and health probes are untouched.\n2. The frontend always hits ``/api/workspaces`` to render the\n   switcher on dashboard load, so the latency cost is amortised.\n3. ``ensure_personal_workspace`` is idempotent and race-safe (it\n   checks for an existing Personal workspace by name+owner before\n   inserting), so multiple concurrent requests converge to one row.","operationId":"list_mine_api_workspaces_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}":{"get":{"tags":["Workspaces"],"summary":"Detail","operationId":"detail_api_workspaces__slug__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceDetailOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/invitations":{"post":{"tags":["Workspaces"],"summary":"Invite","operationId":"invite_api_workspaces__slug__invitations_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/invitations/{invitation_id}":{"delete":{"tags":["Workspaces"],"summary":"Revoke","operationId":"revoke_api_workspaces__slug__invitations__invitation_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","title":"Invitation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/invitations/accept":{"post":{"tags":["Workspaces"],"summary":"Accept","description":"Redeem an invitation token. The caller's user_id (from the\nVercel proxy) becomes a member of the invited workspace.","operationId":"accept_api_workspaces_invitations_accept_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInvitationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInvitationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/members/{member_user_id}":{"patch":{"tags":["Workspaces"],"summary":"Update Member","operationId":"update_member_api_workspaces__slug__members__member_user_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"member_user_id","in":"path","required":true,"schema":{"type":"string","title":"Member User Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRoleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Workspaces"],"summary":"Remove","operationId":"remove_api_workspaces__slug__members__member_user_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"member_user_id","in":"path","required":true,"schema":{"type":"string","title":"Member User Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/members/activity":{"get":{"tags":["Workspaces"],"summary":"Members Activity","operationId":"members_activity_api_workspaces__slug__members_activity_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 timestamp. Counters cover events at or after this point in time. Omit (or pass an empty string) for all-time.","title":"Since"},"description":"ISO 8601 timestamp. Counters cover events at or after this point in time. Omit (or pass an empty string) for all-time."},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberActivityListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/auto-join":{"get":{"tags":["Workspaces"],"summary":"Get Auto Join","operationId":"get_auto_join_api_workspaces__slug__auto_join_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoJoinDomainsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Workspaces"],"summary":"Patch Auto Join","operationId":"patch_auto_join_api_workspaces__slug__auto_join_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAutoJoinDomainsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoJoinDomainsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/settings":{"get":{"tags":["Workspaces"],"summary":"Get Settings","operationId":"get_settings_api_workspaces__slug__settings_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSettingsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Workspaces"],"summary":"Put Settings","operationId":"put_settings_api_workspaces__slug__settings_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkspaceSettingsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSettingsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/data-sync/preview":{"get":{"tags":["Workspaces"],"summary":"Preview Data Sync","description":"How much of the caller's own data would become team data.\n\nRead-only. The UI shows this count next to the button so nobody\npublishes a month of research to find out how much it was.","operationId":"preview_data_sync_api_workspaces__slug__data_sync_preview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Preview Data Sync Api Workspaces  Slug  Data Sync Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/data-sync":{"post":{"tags":["Workspaces"],"summary":"Run Data Sync","description":"Share the caller's own existing data with this workspace.\n\nAny member may do this FOR THEMSELVES — that is the whole point, and\nit is why there is no admin role check: an admin pressing this on\nsomeone else's behalf is exactly what the opt-in exists to prevent.\n``require_workspace_membership`` without a role therefore reads as\nintended, not as a missing gate.\n\nData created from here on is stamped at creation, so this is a\none-time catch-up for what already existed. Idempotent — running it\nagain moves nothing.","operationId":"run_data_sync_api_workspaces__slug__data_sync_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Must be true — this publishes data","default":false,"title":"Confirm"},"description":"Must be true — this publishes data"},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Run Data Sync Api Workspaces  Slug  Data Sync Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/billing/summary":{"get":{"tags":["Workspace Billing"],"summary":"Billing Summary","description":"Current balance + last 5 purchases + month-to-date consumption.\n\nAny workspace member can read this — billing transparency for the\nteam is by design, not an admin-only view. Top-up actions ARE\nadmin-gated (in the Next.js layer, before reaching Stripe).","operationId":"billing_summary_api_workspaces__slug__billing_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingSummaryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/billing/history":{"get":{"tags":["Workspace Billing"],"summary":"Billing History","description":"Full purchase history for the workspace (members only).","operationId":"billing_history_api_workspaces__slug__billing_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingHistoryOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/billing/credit-grant":{"post":{"tags":["Workspace Billing"],"summary":"Credit Grant","description":"Webhook callback — credit a workspace from a confirmed Stripe payment.\n\nCalled by ``kolens-web/app/api/stripe/webhook/route.ts`` after\nStripe's signature verifies. Idempotent on\n``stripe_session_id`` (UNIQUE) — a duplicate delivery short-circuits\nand returns ``credited=False`` with the current balance.\n\nNOT api-key-gated — the Vercel webhook may not have a user session\nwhen Stripe re-delivers, so we gate purely on ``X-Admin-Secret``.","operationId":"credit_grant_api_workspaces__slug__billing_credit_grant_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditGrantRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditGrantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/billing/portal":{"post":{"tags":["Workspace Billing"],"summary":"Billing Portal","description":"Confirm the caller can manage payment methods + return the\nworkspace's Stripe customer id for the Vercel portal-session call.\n\nRole gate: ``admin`` or ``billing`` only. Plain members → 403.\n\nWorkspaces that have never run a checkout don't have a\n``stripe_customer_id`` yet. We still return 200 with the field set\nto null — the Vercel route surfaces a friendly \"run a top-up first\"\nerror instead of opening the portal.","operationId":"billing_portal_api_workspaces__slug__billing_portal_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalCustomerOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/workspaces/{slug}/billing/portal/customer":{"post":{"tags":["Workspace Billing"],"summary":"Set Portal Customer","operationId":"set_portal_customer_api_workspaces__slug__billing_portal_customer_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPortalCustomerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Set Portal Customer Api Workspaces  Slug  Billing Portal Customer Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search-insights":{"post":{"tags":["Search Insights"],"summary":"Create Insight","operationId":"create_insight_api_search_insights_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInsightRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInsightResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search-insights/{insight_id}":{"get":{"tags":["Search Insights"],"summary":"Get Insight","operationId":"get_insight_api_search_insights__insight_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"insight_id","in":"path","required":true,"schema":{"type":"string","title":"Insight Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search-insights/{insight_id}/export/notion":{"post":{"tags":["Search Insights"],"summary":"Export Insight To Notion","description":"Push a ready insight report into a Notion database as a new\npage. The caller supplies a Notion integration token + target\ndatabase id; nothing is persisted server-side.\n\nToken is one-shot per request — never stored. Returns the created\nNotion page id + URL on success.\n\nRate-limited to 5 calls / 60s per workspace (best-effort; keyed on\nworkspace_id once resolved, falling back to api_key + user_id\nbefore workspace resolution).","operationId":"export_insight_to_notion_api_search_insights__insight_id__export_notion_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"insight_id","in":"path","required":true,"schema":{"type":"string","title":"Insight Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotionExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotionExportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search-insights/{insight_id}/regenerate":{"post":{"tags":["Search Insights"],"summary":"Regenerate Insight","description":"Kick off a fresh generation, overwriting the existing row.\n\nEPIC #117 Phase 3 — Regenerate flips the existing insight back to\n``pending`` and schedules a fresh generation against the same\n(workspace_id, keyword, intent) tuple. When the background task\ncompletes it overwrites ``report_json`` + bumps ``generated_at`` so\nthe row id stays stable and any pinned UI / link to the report\ncontinues to work.\n\nSection-scoped regenerate is still in the contract for forwards-\ncompat but not yet wired (Phase 4 polish). Membership gate is\nstrict 404 so we never leak the existence of an insight in a\ndifferent workspace.\n\nRate-limited to 5 calls / 60s per (workspace_id, insight_id). The\nBFF can forward Retry-After to the UI.","operationId":"regenerate_insight_api_search_insights__insight_id__regenerate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"insight_id","in":"path","required":true,"schema":{"type":"string","title":"Insight Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInsightResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations":{"get":{"tags":["CRM"],"summary":"List Route","operationId":"list_route_api_crm_conversations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"assigned_to_user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To User Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["CRM"],"summary":"Create Route","operationId":"create_route_api_crm_conversations_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}":{"get":{"tags":["CRM"],"summary":"Detail Route","operationId":"detail_route_api_crm_conversations__conversation_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetailOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["CRM"],"summary":"Update Route","operationId":"update_route_api_crm_conversations__conversation_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateConversationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}/events":{"post":{"tags":["CRM"],"summary":"Add Event Route","operationId":"add_event_route_api_crm_conversations__conversation_id__events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddEventRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}/tasks":{"post":{"tags":["CRM"],"summary":"Add Task Route","operationId":"add_task_route_api_crm_conversations__conversation_id__tasks_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["CRM"],"summary":"List Conversation Tasks Route","description":"List tasks attached to one conversation. Optional ``?status=``\nfilter accepts a comma-separated list (``pending,done``).","operationId":"list_conversation_tasks_route_api_crm_conversations__conversation_id__tasks_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/tasks":{"post":{"tags":["CRM"],"summary":"Create Task Top Level Route","description":"Convenience: create a task with the conversation id in the body\ninstead of the URL. Same auth + validation as the nested\n``POST /api/crm/conversations/{id}/tasks`` route.","operationId":"create_task_top_level_route_api_crm_tasks_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskTopLevelRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/tasks/{task_id}":{"patch":{"tags":["CRM"],"summary":"Update Task Route","description":"PATCH a scheduled task.\n\nAuth model is identical to the rest of /api/crm: workspace member\ncan edit any task in their workspace. Status transitions are\nrestricted to ``pending → cancelled`` and ``pending → done`` — the\nscheduler owns ``failed``, and re-opening a terminal task is not\nsupported (clients should create a new one).","operationId":"update_task_route_api_crm_tasks__task_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaskRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["CRM"],"summary":"Get Task Route","operationId":"get_task_route_api_crm_tasks__task_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}/ai/draft":{"post":{"tags":["CRM"],"summary":"Ai Draft Route","operationId":"ai_draft_route_api_crm_conversations__conversation_id__ai_draft_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftOutreachRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftOutreachResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}/ai/summarise":{"post":{"tags":["CRM"],"summary":"Ai Summarise Route","operationId":"ai_summarise_route_api_crm_conversations__conversation_id__ai_summarise_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SummariseResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}/ai/next-action":{"post":{"tags":["CRM"],"summary":"Ai Next Action Route","operationId":"ai_next_action_route_api_crm_conversations__conversation_id__ai_next_action_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NextActionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/conversations/{conversation_id}/send":{"post":{"tags":["CRM"],"summary":"Send Email Route","description":"Send an email to the KOL via SendGrid + stamp the outbound event.\n\nReply-To is set to the routing address so any reply comes back\nthrough Inbound Parse. The conversation status auto-promotes from\n``lead`` -> ``contacted`` on the first send (see\n``send_outbound_email`` for the logic).","operationId":"send_email_route_api_crm_conversations__conversation_id__send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/kols/{username}/send-outreach":{"post":{"tags":["CRM"],"summary":"Send Outreach By Kol Route","description":"Find-or-create a conversation for ``username`` and send the email.\n\nConvenience endpoint for the KOL detail page's Outreach panel where\nthe user composes a draft against a KOL handle, not against an\nexisting conversation. Find-or-create on (workspace, platform,\nusername) so repeated sends pile up on the same conversation\ntimeline instead of fragmenting it.\n\nCredits are charged by the Vercel BFF *before* it calls us — if\nSendGrid rejects, we surface 502 and the BFF refunds. Keeping the\ndebit on the BFF side avoids cross-system distributed-transaction\npain (Supabase Postgres vs Railway Postgres are not the same DB).","operationId":"send_outreach_by_kol_route_api_crm_kols__username__send_outreach_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}},{"name":"x-kolens-user-name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendOutreachByKolRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendOutreachByKolResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/bulk-re-engage":{"post":{"tags":["CRM"],"summary":"Bulk Re Engage Route","description":"Bulk-generate re-engagement drafts for a list of KOL usernames.\n\nWorkspace member only. Caps the batch at 50 usernames to keep both\nLLM cost and round-trip predictable; over the cap → 400. Drafts are\npersisted as ``ai_generated=True`` events on the conversation\ntimeline (one conversation per (workspace, platform, handle); the\nservice helper is idempotent on that triple).\n\nPre-charges 0.1 credit per username, rounded up to a whole credit\nvia ``math.ceil``, through the unified ``charge()`` orchestrator\n(workspace pool first, then personal Supabase profile).\nInsufficient credits → 402.","operationId":"bulk_re_engage_route_api_crm_bulk_re_engage_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReEngageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReEngageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/crm/events/{event_id}/send":{"post":{"tags":["CRM"],"summary":"Send Draft Event Route","description":"Send a previously-drafted event via SendGrid.\n\nPhase 5 surface for the bulk re-engage flow: each generated draft\nis a ``DBKolCommunicationEvent`` row (kind='note', direction='outbound',\nsubject + body_text populated). The UI's per-row Send button POSTs\nhere with the recipient address; we resolve the draft → look up its\nconversation → delegate to ``send_outbound_email`` (same path\nPhase 3 uses from ``/conversations/{id}/send``).\n\nWe DO NOT mutate the original draft event — the existing send\npath stamps a new ``email_sent`` event, which is the audit-clean\nbehaviour. The drafted note row stays on the timeline so the user\ncan see \"we composed X, sent Y\".","operationId":"send_draft_event_route_api_crm_events__event_id__send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/authenticity":{"get":{"tags":["Authenticity"],"summary":"Authenticity score (cached, daily) for one KOL","description":"Return the composite authenticity score + per-signal breakdown.\n\nCached daily per (username, platform, ALGORITHM_VERSION). The\nfirst call of the day computes and persists; subsequent calls\nreturn the cached row instantly.\n\nReturns 404 only when the author doesn't exist in our DB at all.\nAccounts that exist but lack data return a row with\n``confidence='insufficient_data'`` — caller renders a grey badge.","operationId":"get_authenticity_score_api_kols__username__authenticity_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","description":"Platform identifier","default":"tiktok","title":"Platform"},"description":"Platform identifier"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"If true, recompute even if a same-day cache row exists.","default":false,"title":"Refresh"},"description":"If true, recompute even if a same-day cache row exists."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthenticityScoreOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kols/{username}/authenticity/dispute":{"post":{"tags":["Authenticity"],"summary":"Mark an authenticity score as contested","description":"Stamp the latest score row as disputed by the caller's workspace.\n\nRequired for legal cover (PM revision): every authenticity score\nmust be challengeable on day 1. We don't *retract* the score on\ndispute — we just attach the operator's note + workspace id so the\nUI can render \"Contested by Acme on 2026-04-01\" alongside the\nbadge, and downstream consumers (filters, alerts) can skip\ndisputed scores if they want.\n\nWorkspace resolution is best-effort: if the caller has no\nworkspace binding the dispute still records (with NULL workspace),\nso legacy CLI keys can flag scores too.","operationId":"dispute_authenticity_score_api_kols__username__authenticity_dispute_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","default":"tiktok","title":"Platform"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}},{"name":"x-kolens-workspace-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-Workspace-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthenticityScoreOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/.well-known/oauth-protected-resource/mcp":{"get":{"tags":["OAuth"],"summary":"Protected Resource Metadata","description":"RFC 9728. Points the client at our authorization server.","operationId":"protected_resource_metadata__well_known_oauth_protected_resource_mcp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"tags":["OAuth"],"summary":"Protected Resource Metadata","description":"RFC 9728. Points the client at our authorization server.","operationId":"protected_resource_metadata__well_known_oauth_protected_resource_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"tags":["OAuth"],"summary":"Authorization Server Metadata","description":"RFC 8414. Tells the client which endpoints exist + which flows\nare supported. Returned shape is what Claude.ai's MCP connector\nexpects to find via the well-known fetch.","operationId":"authorization_server_metadata__well_known_oauth_authorization_server_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/oauth/register":{"post":{"tags":["OAuth"],"summary":"Register","operationId":"register_oauth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/authorize":{"get":{"tags":["OAuth"],"summary":"Authorize Get","description":"Render the consent page. Pre-validates the request so we don't\nshow a form to a request that's going to fail anyway.","operationId":"authorize_get_oauth_authorize_get","parameters":[{"name":"response_type","in":"query","required":true,"schema":{"type":"string","title":"Response Type"}},{"name":"client_id","in":"query","required":true,"schema":{"type":"string","title":"Client Id"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","title":"Redirect Uri"}},{"name":"code_challenge","in":"query","required":true,"schema":{"type":"string","title":"Code Challenge"}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"type":"string","default":"S256","title":"Code Challenge Method"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"mcp","title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["OAuth"],"summary":"Authorize Post","description":"Process the consent form. On success, redirect back to the\nregistered ``redirect_uri`` with ``?code=…&state=…``.","operationId":"authorize_post_oauth_authorize_post","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_authorize_post_oauth_authorize_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/token":{"post":{"tags":["OAuth"],"summary":"Token","description":"RFC 6749 §3.2. Supports ``authorization_code`` + ``refresh_token``\ngrants. Errors follow the OAuth-error JSON shape (``error`` +\n``error_description``).","operationId":"token_oauth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_token_oauth_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/revoke":{"post":{"tags":["OAuth"],"summary":"Revoke","description":"RFC 7009. Revoke an access or refresh token. Returns 200 even\nwhen the token is unknown (per spec — revoking a non-existent\ntoken should not signal anything to a probe). For refresh tokens\nwe also revoke the paired access tokens so a leaked refresh can't\nkeep an attacker minting fresh access tokens after the user disconnects.","operationId":"revoke_oauth_revoke_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_revoke_oauth_revoke_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/connections":{"get":{"tags":["OAuth"],"summary":"List User Connections","description":"List the authorized OAuth clients for the calling user. One\nentry per (client_id, scope) tuple with the count of live access\ntokens + the most recent ``last_used_at`` for that bundle.","operationId":"list_user_connections_oauth_connections_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/connections/{client_id}":{"delete":{"tags":["OAuth"],"summary":"Disconnect Client","description":"Revoke every live token for (user, client). The Disconnect\nbutton on /account/connections hits this; the client's next MCP\ncall gets 401 immediately.","operationId":"disconnect_client_oauth_connections__client_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/credit-status":{"get":{"tags":["Account"],"summary":"Credit Status","operationId":"credit_status_api_me_credit_status_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Credit Status Api Me Credit Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search/intent":{"post":{"tags":["Search"],"summary":"Classify Search Intent","description":"Read a query's shape and say whether it needs a choice first.\n\nDeterministic and free — regex, no model call, no I/O. Safe to call\non every keystroke if a client wants to; safe to skip entirely.","operationId":"classify_search_intent_api_search_intent_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntentRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/search/parse":{"post":{"tags":["Search"],"summary":"Parse Query","operationId":"parse_query_api_search_parse_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/events":{"post":{"tags":["Events"],"summary":"Ingest Event","description":"Record one checkout-intent event against the calling user.\n\n202, not 200: the write is best-effort by design (``record_user_event``\nnever raises), and a caller must not treat a failure to log as a\nfailure of the checkout it was reporting on.","operationId":"ingest_event_api_events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventIn"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Ingest Event Api Events Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recaps/preview":{"get":{"tags":["Admin"],"summary":"Preview Recaps","description":"Render every recap without sending anything.","operationId":"preview_recaps_admin_recaps_preview_get","parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":60,"title":"Lookback Days"}},{"name":"picks","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":5,"title":"Picks"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Preview Recaps Admin Recaps Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recaps/send":{"post":{"tags":["Admin"],"summary":"Send Recaps","description":"Deliver the previewed recaps.\n\n``expect_count`` is the guard that makes this safe to run from a\ndashboard: preview, read the number, pass it back. If the set has\nmoved in between — a new search landed, someone's email resolved —\nthe send aborts instead of quietly mailing a different audience than\nthe one that was reviewed.","operationId":"send_recaps_admin_recaps_send_post","parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":60,"title":"Lookback Days"}},{"name":"picks","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":5,"title":"Picks"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Must be true to send anything","default":false,"title":"Confirm"},"description":"Must be true to send anything"},{"name":"expect_count","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Recipient count you saw in /preview. Mismatch aborts.","title":"Expect Count"},"description":"Recipient count you saw in /preview. Mismatch aborts."},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Send Recaps Admin Recaps Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/expansions":{"get":{"tags":["Expansions"],"summary":"List My Offers","description":"Pending offers for the caller, newest first.","operationId":"list_my_offers_api_expansions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response List My Offers Api Expansions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/expansions/{offer_id}/unlock":{"post":{"tags":["Expansions"],"summary":"Unlock Offer","description":"Charge for an offer and grant its creators to the caller.","operationId":"unlock_offer_api_expansions__offer_id__unlock_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string","title":"Offer Id"}},{"name":"x-kolens-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Kolens-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Unlock Offer Api Expansions  Offer Id  Unlock Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/expansions/plan":{"get":{"tags":["Admin"],"summary":"Expansion Plan","description":"What a sweep would run. Spends nothing on scraping.","operationId":"expansion_plan_admin_expansions_plan_get","parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":60,"title":"Lookback Days"}},{"name":"max_scrapes","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Hard ceiling on scrapes this sweep would launch.","default":10,"title":"Max Scrapes"},"description":"Hard ceiling on scrapes this sweep would launch."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Plan for one user instead of everyone eligible.","title":"User Id"},"description":"Plan for one user instead of everyone eligible."},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Expansion Plan Admin Expansions Plan Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/expansions/execute":{"post":{"tags":["Admin"],"summary":"Execute Sweep","description":"Queue the planned scrapes on our own budget.\n\nRe-plans rather than trusting a plan posted back: a client-supplied\nlist of searches to run would be a way to spend our Apify balance on\narbitrary keywords. ``expect_scrapes`` is how the reviewed plan is\ntied to this call — the LLM is non-deterministic, so re-planning can\nlegitimately produce a different set, and a mismatch means what runs\nis not what was read.","operationId":"execute_sweep_admin_expansions_execute_post","parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":60,"title":"Lookback Days"}},{"name":"max_scrapes","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Max Scrapes"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Must be true to spend anything","default":false,"title":"Confirm"},"description":"Must be true to spend anything"},{"name":"expect_scrapes","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Scrape count you saw in /plan. Mismatch aborts.","title":"Expect Scrapes"},"description":"Scrape count you saw in /plan. Mismatch aborts."},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Execute Sweep Admin Expansions Execute Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/expansions/settle":{"post":{"tags":["Admin"],"summary":"Settle","description":"Convert finished expansion jobs into offers.\n\nSafe and idempotent — it only reads job status and writes offer\nrows, so it can be polled while a sweep is still running.","operationId":"settle_admin_expansions_settle_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Settle Admin Expansions Settle Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/tiktok/tcm-probe":{"post":{"tags":["Admin"],"summary":"Tcm Probe","description":"Probe TCM creator discovery and report exactly what TikTok says.","operationId":"tcm_probe_admin_tiktok_tcm_probe_post","parameters":[{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated ISO-2 codes; defaults to US,TW,JP,SG,GB","title":"Markets"},"description":"Comma-separated ISO-2 codes; defaults to US,TW,JP,SG,GB"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":5,"title":"Page Size"}},{"name":"x-admin-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Tcm Probe Admin Tiktok Tcm Probe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AcceptInvitationOut":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"workspace_slug":{"type":"string","title":"Workspace Slug"},"role":{"type":"string","enum":["admin","billing","member"],"title":"Role"},"joined_at":{"type":"string","format":"date-time","title":"Joined At"}},"type":"object","required":["workspace_id","workspace_slug","role","joined_at"],"title":"AcceptInvitationOut"},"AcceptInvitationRequest":{"properties":{"token":{"type":"string","maxLength":128,"minLength":8,"title":"Token"}},"type":"object","required":["token"],"title":"AcceptInvitationRequest"},"ActivityEventOut":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","title":"Kind"},"summary":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Summary"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Seen At"},"unseen":{"type":"boolean","title":"Unseen","default":false}},"type":"object","required":["id","kind"],"title":"ActivityEventOut"},"ActivitySummaryOut":{"properties":{"period":{"type":"string","title":"Period"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights","default":[]},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At"}},"type":"object","required":["period"],"title":"ActivitySummaryOut","description":"The cached LLM ``DBKolActivitySummary`` for this KOL, if one exists."},"ActivitySummaryResponse":{"properties":{"summary":{"type":"string","title":"Summary"},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights"},"talking_points":{"items":{"type":"string"},"type":"array","title":"Talking Points"},"competitor_mentions":{"items":{"type":"string"},"type":"array","title":"Competitor Mentions"},"period":{"type":"string","title":"Period"},"generated_at":{"type":"string","title":"Generated At"},"videos_analyzed":{"type":"integer","title":"Videos Analyzed"},"cost_credits":{"type":"integer","title":"Cost Credits"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["summary","highlights","talking_points","competitor_mentions","period","generated_at","videos_analyzed","cost_credits","cached"],"title":"ActivitySummaryResponse"},"AddEventRequest":{"properties":{"kind":{"type":"string","enum":["note","call_log"],"title":"Kind"},"direction":{"type":"string","enum":["outbound","inbound","internal"],"title":"Direction","default":"internal"},"body":{"type":"string","maxLength":20000,"title":"Body","default":""}},"type":"object","required":["kind"],"title":"AddEventRequest"},"AggregatedCreatorOut":{"properties":{"handle":{"type":"string","title":"Handle"},"primary_platform":{"type":"string","title":"Primary Platform"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"total_reach":{"type":"integer","title":"Total Reach"},"platform_count":{"type":"integer","title":"Platform Count"},"platforms":{"items":{"$ref":"#/components/schemas/PlatformPresenceOut"},"type":"array","title":"Platforms"},"confidence":{"type":"string","title":"Confidence"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"summary":{"type":"string","title":"Summary","default":""}},"type":"object","required":["handle","primary_platform","total_reach","platform_count","platforms","confidence"],"title":"AggregatedCreatorOut"},"AggregatedDirectoryResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/AggregatedCreatorOut"},"type":"array","title":"Entries"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["entries","count"],"title":"AggregatedDirectoryResponse"},"AlertIdentity":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"verified":{"type":"boolean","title":"Verified","default":false},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"avatar_url_cached":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url Cached"}},"type":"object","required":["username"],"title":"AlertIdentity","description":"KOL identity attached to a rising_kol / trending_video alert so the\n/alerts feed can render the shared rich KOLCard (avatar + nickname +\nfollower/views/engagement stats) without a per-row follow-up request\n(Issue #4). ``None`` on the alert when no canonical ``DBAuthor`` row\nexists for the subject (legacy / never-scraped handle).\n\nThe stat fields below (``follower_count`` … ``avatar_url_cached``) are\nadditive/optional — back-compat for any caller still reading only the\noriginal identity shape — and feed the shared ``KOLCard`` on the web\nalerts surface so it matches the rest of the app."},"AlertPreferenceIn":{"properties":{"email_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Enabled"},"email_to":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Email To"},"webhook_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Webhook Enabled"},"webhook_url":{"anyOf":[{"type":"string","maxLength":1024},{"type":"null"}],"title":"Webhook Url"},"webhook_secret":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Webhook Secret"},"muted_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Muted Types"},"rate_per_hour":{"anyOf":[{"type":"integer","maximum":1000.0,"minimum":0.0},{"type":"null"}],"title":"Rate Per Hour"},"preferred_locale":{"anyOf":[{"type":"string","maxLength":10},{"type":"null"}],"title":"Preferred Locale","description":"BCP-47 tag from the supported set: en | zh-CN | ja"},"rising_score_threshold":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Rising Score Threshold"},"rising_follower_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Rising Follower Pct"},"rising_follower_delta":{"anyOf":[{"type":"integer","maximum":100000000.0,"minimum":0.0},{"type":"null"}],"title":"Rising Follower Delta"},"rising_view_growth_pct":{"anyOf":[{"type":"number","maximum":10000.0,"minimum":0.0},{"type":"null"}],"title":"Rising View Growth Pct"},"rising_lookback_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Rising Lookback Days"}},"type":"object","title":"AlertPreferenceIn","description":"Patch shape for PUT — every field optional. We diff vs the row\nwe already have so partial updates from the settings form don't\naccidentally clear a sibling field."},"AlertPreferenceOut":{"properties":{"email_enabled":{"type":"boolean","title":"Email Enabled"},"email_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email To"},"webhook_enabled":{"type":"boolean","title":"Webhook Enabled"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"},"webhook_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Secret"},"muted_types":{"items":{"type":"string"},"type":"array","title":"Muted Types"},"rate_per_hour":{"type":"integer","title":"Rate Per Hour"},"preferred_locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preferred Locale"},"supported_locales":{"items":{"type":"string"},"type":"array","title":"Supported Locales"},"rising_score_threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rising Score Threshold"},"rising_follower_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rising Follower Pct"},"rising_follower_delta":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rising Follower Delta"},"rising_view_growth_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rising View Growth Pct"},"rising_lookback_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rising Lookback Days"},"threshold_defaults":{"anyOf":[{"$ref":"#/components/schemas/AlertThresholdDefaults"},{"type":"null"}]},"last_delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Delivered At"},"last_email_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Email Error"},"last_webhook_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Webhook Error"}},"type":"object","required":["email_enabled","webhook_enabled","rate_per_hour"],"title":"AlertPreferenceOut"},"AlertThresholdDefaults":{"properties":{"rising_follower_pct":{"type":"number","title":"Rising Follower Pct"},"rising_follower_delta":{"type":"integer","title":"Rising Follower Delta"},"rising_view_growth_pct":{"type":"number","title":"Rising View Growth Pct"},"rising_lookback_days":{"type":"integer","title":"Rising Lookback Days"},"rising_score_threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rising Score Threshold"}},"type":"object","required":["rising_follower_pct","rising_follower_delta","rising_view_growth_pct","rising_lookback_days"],"title":"AlertThresholdDefaults","description":"The global config defaults for the configurable rising_kol\nthresholds (#661). Echoed alongside the per-user overrides so the\nsettings UI can show \"default: 5%\" placeholders and reset-to-default\naffordances without hardcoding the numbers in the frontend."},"ApplyMigrationRequest":{"properties":{"filename":{"type":"string","title":"Filename","description":"Migration filename, e.g. 006_translate_kol_fields.sql"},"target":{"type":"string","title":"Target","description":"Which database: 'app' (Neon, DATABASE_URL) or 'supabase'.","default":"app"}},"type":"object","required":["filename"],"title":"ApplyMigrationRequest"},"AssignExportRequest":{"properties":{"assignees":{"items":{"type":"string"},"type":"array","title":"Assignees"},"strategy":{"type":"string","title":"Strategy","default":"round_robin"},"filters":{"anyOf":[{"$ref":"#/components/schemas/AssignFilters"},{"type":"null"}]}},"type":"object","required":["assignees"],"title":"AssignExportRequest"},"AssignFilters":{"properties":{"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"min_followers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Followers"},"max_followers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Followers"},"require_email":{"type":"boolean","title":"Require Email","default":false},"tier_in":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tier In"},"triage_state_in":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Triage State In"}},"type":"object","title":"AssignFilters","description":"Subset of filters available when carving up a list for assignment."},"AttachVideoIn":{"properties":{"video_id":{"type":"string","maxLength":50,"minLength":1,"title":"Video Id"}},"type":"object","required":["video_id"],"title":"AttachVideoIn"},"AudienceData":{"properties":{"gender":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Gender"},"age":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Age"},"top_countries":{"anyOf":[{"items":{"type":"object"},"type":"array"},{"type":"null"}],"title":"Top Countries"},"top_interests":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Top Interests"}},"type":"object","title":"AudienceData","description":"Normalized audience demographics — present only when enrichment has been run."},"AudienceInsightsRequest":{"properties":{"snapshot":{"type":"object","title":"Snapshot"}},"type":"object","required":["snapshot"],"title":"AudienceInsightsRequest"},"AudienceInsightsResponse":{"properties":{"summary":{"type":"string","title":"Summary"},"segments":{"items":{"$ref":"#/components/schemas/AudienceSegmentOut"},"type":"array","title":"Segments"},"content_angles":{"items":{"type":"string"},"type":"array","title":"Content Angles"},"outreach_angle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Outreach Angle"},"caveats":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caveats"},"generated_at":{"type":"string","title":"Generated At"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["summary","segments","content_angles","generated_at","cached"],"title":"AudienceInsightsResponse"},"AudienceSegmentOut":{"properties":{"label":{"type":"string","title":"Label"},"reasoning":{"type":"string","title":"Reasoning"}},"type":"object","required":["label","reasoning"],"title":"AudienceSegmentOut"},"AudienceSnapshotOut":{"properties":{"id":{"type":"string","title":"Id"},"username":{"type":"string","title":"Username"},"sampled_at":{"type":"string","title":"Sampled At"},"sample_size":{"type":"integer","title":"Sample Size"},"platform":{"type":"string","title":"Platform","default":"tiktok"},"sample_method":{"type":"string","title":"Sample Method","default":"followers"},"countries":{"additionalProperties":{"type":"number"},"type":"object","title":"Countries"},"languages":{"additionalProperties":{"type":"number"},"type":"object","title":"Languages"},"follower_tier_dist":{"additionalProperties":{"type":"number"},"type":"object","title":"Follower Tier Dist"},"top_hashtags":{"items":{"type":"object"},"type":"array","title":"Top Hashtags"},"active_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Active Pct"},"raw_count_active":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Raw Count Active"},"raw_count_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Raw Count Total"},"confidence_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence Pct"},"error_margin_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Error Margin Pct"},"charged_credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Charged Credits"}},"type":"object","required":["id","username","sampled_at","sample_size"],"title":"AudienceSnapshotOut"},"AudienceSnapshotRequest":{"properties":{"sample_size":{"type":"integer","title":"Sample Size","description":"200 | 500 | 2000","default":500}},"type":"object","title":"AudienceSnapshotRequest","description":"Body for POST /api/kols/{username}/audience.\n\nSample sizes are tiered to keep Apify spend predictable; only the\nthree values from the design doc are accepted. Anything else 422s."},"AuthenticityScoreOut":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"type":"string","title":"Platform"},"algorithm_version":{"type":"string","title":"Algorithm Version"},"snapshot_date":{"type":"string","title":"Snapshot Date"},"score":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Score"},"confidence":{"type":"string","title":"Confidence"},"signals":{"additionalProperties":{"$ref":"#/components/schemas/SignalDetailOut"},"type":"object","title":"Signals"},"disputed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disputed At"},"disputed_by_workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disputed By Workspace Id"},"dispute_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispute Note"},"computed_at":{"type":"string","title":"Computed At"},"summary":{"type":"string","title":"Summary"}},"type":"object","required":["username","platform","algorithm_version","snapshot_date","score","confidence","signals","computed_at","summary"],"title":"AuthenticityScoreOut","description":"Composite authenticity score + per-signal breakdown.\n\nNote on copy: every user-visible string here says \"authenticity\"\nor \"signal\" — never \"fake\" / \"bot\" / \"fraud\". The PM revision is\nfirm on this: the score lives in legal grey area and the wording\nis the cover."},"AutoJoinDomainsOut":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"slug":{"type":"string","title":"Slug"},"domains":{"items":{"type":"string"},"type":"array","title":"Domains"}},"type":"object","required":["workspace_id","slug","domains"],"title":"AutoJoinDomainsOut"},"BackgroundResearchResponse":{"properties":{"summary_markdown":{"type":"string","title":"Summary Markdown"},"sources":{"items":{"$ref":"#/components/schemas/BackgroundSourceOut"},"type":"array","title":"Sources"},"generated_at":{"type":"string","title":"Generated At"},"cost_credits":{"type":"integer","title":"Cost Credits"},"locale":{"type":"string","title":"Locale"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["summary_markdown","sources","generated_at","cost_credits","locale","cached"],"title":"BackgroundResearchResponse"},"BackgroundSourceOut":{"properties":{"url":{"type":"string","title":"Url"},"title":{"type":"string","title":"Title","default":""},"snippet":{"type":"string","title":"Snippet","default":""}},"type":"object","required":["url"],"title":"BackgroundSourceOut"},"BatchEnrichRequest":{"properties":{"only_missing":{"type":"boolean","title":"Only Missing","description":"Skip members that already have a verified email.","default":true},"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","description":"Max members to process this call.","default":50}},"type":"object","title":"BatchEnrichRequest"},"BillingHistoryOut":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"slug":{"type":"string","title":"Slug"},"purchases":{"items":{"$ref":"#/components/schemas/PurchaseOut"},"type":"array","title":"Purchases"}},"type":"object","required":["workspace_id","slug"],"title":"BillingHistoryOut"},"BillingSummaryOut":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"slug":{"type":"string","title":"Slug"},"balance":{"type":"number","title":"Balance"},"monthly_consumed":{"type":"number","title":"Monthly Consumed"},"recent_purchases":{"items":{"$ref":"#/components/schemas/PurchaseOut"},"type":"array","title":"Recent Purchases"}},"type":"object","required":["workspace_id","slug","balance","monthly_consumed"],"title":"BillingSummaryOut"},"BoardRequestIn":{"properties":{"market":{"type":"string","maxLength":8,"title":"Market","description":"ISO-3166 alpha-2, e.g. MY"},"niche":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"title":"Niche"},"raw_request":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Raw Request"},"visitor_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Visitor Id"},"conversation_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Conversation Id"},"page_path":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Page Path"},"locale":{"anyOf":[{"type":"string","maxLength":10},{"type":"null"}],"title":"Locale"},"email":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Email"}},"type":"object","required":["market"],"title":"BoardRequestIn"},"BoardRequestOut":{"properties":{"outcome":{"type":"string","title":"Outcome"},"market":{"type":"string","title":"Market"},"board_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Url"},"captured_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Captured At"},"rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["outcome","market"],"title":"BoardRequestOut"},"BoardSummaryOut":{"properties":{"country":{"type":"string","title":"Country"},"window":{"type":"string","title":"Window"},"total":{"type":"integer","title":"Total"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["country","window","total"],"title":"BoardSummaryOut"},"Body_authorize_post_oauth_authorize_post":{"properties":{"client_id":{"type":"string","title":"Client Id"},"redirect_uri":{"type":"string","title":"Redirect Uri"},"code_challenge":{"type":"string","title":"Code Challenge"},"code_challenge_method":{"type":"string","title":"Code Challenge Method","default":"S256"},"state":{"type":"string","title":"State","default":""},"scope":{"type":"string","title":"Scope","default":"mcp"},"api_key":{"type":"string","title":"Api Key"}},"type":"object","required":["client_id","redirect_uri","code_challenge","api_key"],"title":"Body_authorize_post_oauth_authorize_post"},"Body_enrich_contact_api_kols__username__enrich_contact_post":{"properties":{"force":{"type":"boolean","title":"Force","description":"Bypass the cache + re-run upstream","default":false}},"type":"object","title":"Body_enrich_contact_api_kols__username__enrich_contact_post"},"Body_revoke_oauth_revoke_post":{"properties":{"token":{"type":"string","title":"Token"},"token_type_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Type Hint"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"}},"type":"object","required":["token"],"title":"Body_revoke_oauth_revoke_post"},"Body_token_oauth_token_post":{"properties":{"grant_type":{"type":"string","title":"Grant Type"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"code_verifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code Verifier"},"redirect_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redirect Uri"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"}},"type":"object","required":["grant_type"],"title":"Body_token_oauth_token_post"},"BulkHandleAcceptedOut":{"properties":{"handle":{"type":"string","title":"Handle"},"job_id":{"type":"string","title":"Job Id"}},"type":"object","required":["handle","job_id"],"title":"BulkHandleAcceptedOut"},"BulkHandleImportRequest":{"properties":{"handles":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Handles","description":"Up to 50 TikTok handles / URLs / @user strings"},"max_videos_per_handle":{"type":"integer","maximum":200.0,"minimum":10.0,"title":"Max Videos Per Handle","default":30},"target_list_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Target List Id"}},"type":"object","required":["handles"],"title":"BulkHandleImportRequest"},"BulkHandleImportResponse":{"properties":{"accepted":{"items":{"$ref":"#/components/schemas/BulkHandleAcceptedOut"},"type":"array","title":"Accepted"},"rejected":{"items":{"$ref":"#/components/schemas/BulkHandleRejectedOut"},"type":"array","title":"Rejected"},"duplicates_dropped":{"type":"integer","title":"Duplicates Dropped"},"total_credits_charged":{"type":"integer","title":"Total Credits Charged"},"target_list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target List Id"}},"type":"object","required":["accepted","rejected","duplicates_dropped","total_credits_charged"],"title":"BulkHandleImportResponse"},"BulkHandleRejectedOut":{"properties":{"handle":{"type":"string","title":"Handle"},"reason":{"type":"string","title":"Reason"}},"type":"object","required":["handle","reason"],"title":"BulkHandleRejectedOut"},"BulkReEngageRequest":{"properties":{"usernames":{"items":{"type":"string"},"type":"array","maxItems":200,"minItems":1,"title":"Usernames"},"goal":{"type":"string","enum":["reactivate","promote_new_product","feedback"],"title":"Goal","default":"reactivate"},"context":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Context"}},"type":"object","required":["usernames"],"title":"BulkReEngageRequest","description":"``POST /api/crm/bulk-re-engage`` body.\n\n``usernames`` is the list of KOL handles the user selected on /kols.\n``goal`` steers the AI compose prompt; ``context`` is free-form\nworkspace context (e.g. \"launching a new phone case next month\").\nThe route caps ``usernames`` at 50 (over -> 400 too_many_targets);\npricing is 0.1 credit per username, rounded up to a whole credit\nvia the existing ``charge()`` orchestrator."},"BulkReEngageResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BulkReEngageRow"},"type":"array","title":"Results"},"cost_credits":{"type":"integer","title":"Cost Credits"},"charged_from":{"type":"string","title":"Charged From"}},"type":"object","required":["results","cost_credits","charged_from"],"title":"BulkReEngageResponse"},"BulkReEngageRow":{"properties":{"username":{"type":"string","title":"Username"},"kol_conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kol Conversation Id"},"draft_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Draft Event Id"},"draft_subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Draft Subject"},"draft_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Draft Body"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["username"],"title":"BulkReEngageRow","description":"One row of the bulk-re-engage response.\n\nOn success: every field except ``error`` is populated. On per-row\nfailure (author missing, AI call exploded, etc.) only ``username``\nand ``error`` are set — the route always returns 200 with a mix of\nsuccess + error rows so the UI can render a \"13/15 drafts ready\"\nsummary without needing a second round-trip."},"BulkReadRequest":{"properties":{"ids":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Ids"},"is_read":{"type":"boolean","title":"Is Read","default":true}},"type":"object","required":["ids"],"title":"BulkReadRequest","description":"Mark several alerts read/unread in one call (multi-select on the\n/alerts feed). ``is_read`` defaults to True so the common\n\"mark selected as read\" path can omit it."},"BulkReadResponse":{"properties":{"updated":{"type":"integer","title":"Updated"},"is_read":{"type":"boolean","title":"Is Read"}},"type":"object","required":["updated","is_read"],"title":"BulkReadResponse"},"CampaignIn":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"product":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Product"},"budget_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Budget Amount"},"currency":{"type":"string","maxLength":3,"minLength":3,"title":"Currency","default":"USD"},"target_regions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Regions"},"starts_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Starts On"},"ends_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ends On"},"status":{"type":"string","title":"Status","default":"active"},"note":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Note"}},"type":"object","required":["name"],"title":"CampaignIn"},"CampaignPatch":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"product":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Product"},"budget_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Budget Amount"},"currency":{"anyOf":[{"type":"string","maxLength":3,"minLength":3},{"type":"null"}],"title":"Currency"},"target_regions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Regions"},"starts_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Starts On"},"ends_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ends On"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"note":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Note"}},"type":"object","title":"CampaignPatch"},"CampaignTagRequest":{"properties":{"campaign_tag":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Campaign Tag","description":"Override the detected campaign label. Null/empty restores detection."}},"type":"object","title":"CampaignTagRequest"},"CancelJobResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"cancel_requested":{"type":"boolean","title":"Cancel Requested"}},"type":"object","required":["job_id","status","stage","cancel_requested"],"title":"CancelJobResponse"},"ChangeRoleRequest":{"properties":{"role":{"type":"string","enum":["admin","billing","member"],"title":"Role"}},"type":"object","required":["role"],"title":"ChangeRoleRequest"},"ChangeWindowOut":{"properties":{"window_days":{"type":"integer","title":"Window Days"},"snapshots_count":{"type":"integer","title":"Snapshots Count"},"follower_delta":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Delta"},"follower_delta_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Follower Delta Pct"},"avg_views_delta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views Delta"},"avg_views_delta_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views Delta Pct"},"avg_views_unreliable":{"type":"boolean","title":"Avg Views Unreliable","default":false},"engagement_rate_delta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate Delta"}},"type":"object","required":["window_days","snapshots_count"],"title":"ChangeWindowOut","description":"Snapshot-derived deltas over one time window (7d or 30d)."},"ChangesOut":{"properties":{"windows":{"additionalProperties":{"$ref":"#/components/schemas/ChangeWindowOut"},"type":"object","title":"Windows","default":{}},"recent_events":{"items":{"$ref":"#/components/schemas/ActivityEventOut"},"type":"array","title":"Recent Events","default":[]},"activity_summary":{"anyOf":[{"$ref":"#/components/schemas/ActivitySummaryOut"},{"type":"null"}]}},"type":"object","title":"ChangesOut"},"CollectionMemberOut":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"type":"string","title":"Platform"},"display_name":{"type":"string","title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"follower_count":{"type":"integer","title":"Follower Count"},"tier":{"type":"string","title":"Tier"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"verified":{"type":"boolean","title":"Verified","default":false},"has_public_page":{"type":"boolean","title":"Has Public Page","default":false}},"type":"object","required":["username","platform","display_name","follower_count","tier"],"title":"CollectionMemberOut"},"CollectionOut":{"properties":{"slug":{"type":"string","title":"Slug"},"vertical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vertical"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"title":{"type":"string","title":"Title"},"subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subtitle"},"total":{"type":"integer","title":"Total"},"tiers":{"additionalProperties":{"type":"integer"},"type":"object","title":"Tiers"},"members":{"items":{"$ref":"#/components/schemas/CollectionMemberOut"},"type":"array","title":"Members"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["slug","title","total","tiers","members"],"title":"CollectionOut"},"CollectionSummaryOut":{"properties":{"slug":{"type":"string","title":"Slug"},"vertical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vertical"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"title":{"type":"string","title":"Title"},"subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subtitle"},"total":{"type":"integer","title":"Total"},"preview":{"items":{"$ref":"#/components/schemas/CollectionMemberOut"},"type":"array","title":"Preview"}},"type":"object","required":["slug","title","total","preview"],"title":"CollectionSummaryOut"},"ContactSchema":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"instagram":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instagram"},"youtube":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube"},"linktree_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linktree Url"}},"type":"object","title":"ContactSchema"},"ConversationDetailOut":{"properties":{"id":{"type":"string","title":"Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"kol_username":{"type":"string","title":"Kol Username"},"kol_platform":{"type":"string","title":"Kol Platform"},"status":{"type":"string","title":"Status"},"assigned_to_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To User Id"},"created_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By User Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_event_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Event At"},"last_event_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Event Kind"},"events":{"items":{"$ref":"#/components/schemas/EventOut"},"type":"array","title":"Events"},"tasks":{"items":{"$ref":"#/components/schemas/TaskOut"},"type":"array","title":"Tasks"}},"type":"object","required":["id","workspace_id","kol_username","kol_platform","status","created_at","updated_at","events","tasks"],"title":"ConversationDetailOut"},"ConversationListOut":{"properties":{"conversations":{"items":{"$ref":"#/components/schemas/ConversationOut"},"type":"array","title":"Conversations"}},"type":"object","required":["conversations"],"title":"ConversationListOut"},"ConversationOut":{"properties":{"id":{"type":"string","title":"Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"kol_username":{"type":"string","title":"Kol Username"},"kol_platform":{"type":"string","title":"Kol Platform"},"status":{"type":"string","title":"Status"},"assigned_to_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To User Id"},"created_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By User Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_event_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Event At"},"last_event_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Event Kind"}},"type":"object","required":["id","workspace_id","kol_username","kol_platform","status","created_at","updated_at"],"title":"ConversationOut"},"CookieFileResponse":{"properties":{"id":{"type":"string","title":"Id"},"api_key_id":{"type":"string","title":"Api Key Id"},"filename":{"type":"string","title":"Filename"},"upload_time":{"type":"string","format":"date-time","title":"Upload Time"},"is_active":{"type":"boolean","title":"Is Active"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"last_used_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used Time"},"cookie_count":{"type":"integer","title":"Cookie Count","description":"Number of cookies in the file"}},"type":"object","required":["id","api_key_id","filename","upload_time","is_active","description","last_used_time","cookie_count"],"title":"CookieFileResponse","description":"Response model for cookie file information."},"CookieListResponse":{"properties":{"cookies":{"items":{"$ref":"#/components/schemas/CookieFileResponse"},"type":"array","title":"Cookies"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["cookies","total"],"title":"CookieListResponse","description":"Response model for listing cookie files."},"CookieUploadRequest":{"properties":{"filename":{"type":"string","minLength":1,"title":"Filename","description":"Name of the cookie file"},"cookies":{"items":{"type":"object"},"type":"array","minItems":1,"title":"Cookies","description":"Array of cookie objects (Playwright format) — must be non-empty"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional description"}},"type":"object","required":["filename","cookies"],"title":"CookieUploadRequest","description":"Request body for uploading a cookie file.","example":{"cookies":[{"domain":".tiktok.com","expires":1735689600,"httpOnly":true,"name":"sessionid","path":"/","sameSite":"Lax","secure":true,"value":"abc123..."}],"description":"Production account cookies","filename":"tiktok_cookies_2026.json"}},"CreateConversationRequest":{"properties":{"kol_username":{"type":"string","maxLength":120,"minLength":1,"title":"Kol Username"},"kol_platform":{"type":"string","enum":["tiktok","instagram"],"title":"Kol Platform","default":"tiktok"},"status":{"type":"string","enum":["lead","contacted","responded","negotiating","partner","declined","archived"],"title":"Status","default":"lead"},"assigned_to_user_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Assigned To User Id"}},"type":"object","required":["kol_username"],"title":"CreateConversationRequest"},"CreateInsightRequest":{"properties":{"keyword":{"type":"string","maxLength":200,"minLength":1,"title":"Keyword"},"intent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Intent","description":"Optional explicit intent. When omitted, the classifier decides. Valid values: kol_discovery, content_inspiration, competitive_research, trend_research, unclassified"},"context":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Context"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"}},"type":"object","required":["keyword"],"title":"CreateInsightRequest"},"CreateInsightResponse":{"properties":{"insight_id":{"type":"string","title":"Insight Id"},"status":{"type":"string","title":"Status"},"intent":{"type":"string","title":"Intent"},"keyword":{"type":"string","title":"Keyword"},"cached":{"type":"boolean","title":"Cached","default":false}},"type":"object","required":["insight_id","status","intent","keyword"],"title":"CreateInsightResponse"},"CreateKeyRequest":{"properties":{"label":{"type":"string","title":"Label","default":""},"user_id":{"type":"string","title":"User Id","default":""}},"type":"object","title":"CreateKeyRequest"},"CreateKeyResponse":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"message":{"type":"string","title":"Message"}},"type":"object","required":["key","label","message"],"title":"CreateKeyResponse"},"CreateTaskRequest":{"properties":{"title":{"type":"string","maxLength":255,"minLength":1,"title":"Title"},"due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Due At"},"assignee_user_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Assignee User Id"},"recurring_rule_text":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Recurring Rule Text"},"action_kind":{"type":"string","enum":["reminder","send_template","next_action_recompute"],"title":"Action Kind","default":"reminder"},"action_payload":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Action Payload"},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At"}},"type":"object","required":["title"],"title":"CreateTaskRequest"},"CreateTaskTopLevelRequest":{"properties":{"title":{"type":"string","maxLength":255,"minLength":1,"title":"Title"},"due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Due At"},"assignee_user_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Assignee User Id"},"recurring_rule_text":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Recurring Rule Text"},"action_kind":{"type":"string","enum":["reminder","send_template","next_action_recompute"],"title":"Action Kind","default":"reminder"},"action_payload":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Action Payload"},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At"},"conversation_id":{"type":"string","maxLength":36,"minLength":1,"title":"Conversation Id"}},"type":"object","required":["title","conversation_id"],"title":"CreateTaskTopLevelRequest","description":"``POST /api/crm/tasks`` body — same fields as the nested\ncreate-task plus the conversation id (the nested route takes that\nfrom the path)."},"CreateWorkspaceRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"slug":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Slug"},"plan":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Plan","default":"free"}},"type":"object","required":["name"],"title":"CreateWorkspaceRequest"},"CreditGrantRequest":{"properties":{"stripe_session_id":{"type":"string","maxLength":128,"minLength":4,"title":"Stripe Session Id"},"credits":{"type":"number","exclusiveMinimum":0.0,"title":"Credits"},"amount_cents":{"type":"integer","minimum":0.0,"title":"Amount Cents"},"user_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"User Id"},"receipt_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Receipt Url"}},"type":"object","required":["stripe_session_id","credits","amount_cents"],"title":"CreditGrantRequest"},"CreditGrantResponse":{"properties":{"credited":{"type":"boolean","title":"Credited"},"workspace_id":{"type":"string","title":"Workspace Id"},"balance":{"type":"number","title":"Balance"},"purchase_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purchase Id"}},"type":"object","required":["credited","workspace_id","balance"],"title":"CreditGrantResponse","description":"Return shape for the webhook callback.\n\n``credited`` is False when the request was deduplicated (replayed\nwebhook with the same ``stripe_session_id``); in that case\n``balance`` reflects the existing post-credit balance, so the\nwebhook can log \"already processed\" without re-crediting."},"CreditsResponse":{"properties":{"credits_available":{"type":"number","title":"Credits Available"},"credits_used":{"type":"number","title":"Credits Used"}},"type":"object","required":["credits_available","credits_used"],"title":"CreditsResponse"},"CrossPlatformRequest":{"properties":{"platforms":{"items":{"type":"string"},"type":"array","title":"Platforms","description":"Target platforms to resolve + merge (youtube / facebook / instagram)."},"force":{"type":"boolean","title":"Force","description":"Bypass the cache + re-resolve","default":false}},"type":"object","title":"CrossPlatformRequest"},"CrossPlatformResolveResponse":{"properties":{"platforms":{"items":{"type":"object"},"type":"array","title":"Platforms"},"reach_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reach Total"},"native_followers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Native Followers"},"confidence":{"type":"string","title":"Confidence"},"cost_credits":{"type":"integer","title":"Cost Credits"},"cached":{"type":"boolean","title":"Cached"},"summary":{"type":"string","title":"Summary"},"risk_flags":{"items":{"type":"string"},"type":"array","title":"Risk Flags","default":[]},"next_actions":{"items":{"type":"string"},"type":"array","title":"Next Actions","default":[]}},"type":"object","required":["platforms","confidence","cost_credits","cached","summary"],"title":"CrossPlatformResolveResponse"},"CrossPlatformResponse":{"properties":{"primary":{"$ref":"#/components/schemas/PlatformPresence"},"linked":{"items":{"$ref":"#/components/schemas/PlatformPresence"},"type":"array","title":"Linked","default":[]},"contacts":{"type":"object","title":"Contacts","default":{}}},"type":"object","required":["primary"],"title":"CrossPlatformResponse"},"CurrentStatsOut":{"properties":{"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"}},"type":"object","title":"CurrentStatsOut"},"DeepOverviewResponse":{"properties":{"overall_summary":{"type":"string","title":"Overall Summary"},"content_themes":{"items":{"type":"string"},"type":"array","title":"Content Themes"},"audience_signal":{"type":"string","title":"Audience Signal"},"partnership_signals":{"items":{"type":"string"},"type":"array","title":"Partnership Signals"},"red_flags":{"items":{"type":"string"},"type":"array","title":"Red Flags"},"fit_for":{"items":{"type":"string"},"type":"array","title":"Fit For"},"recommendation_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recommendation Score"},"content_fit_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Content Fit Score"},"audience_fit_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Fit Score"},"risk_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Risk Score"},"score_rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Score Rationale"},"brand_profile_status":{"type":"string","title":"Brand Profile Status","default":"missing"},"workspace_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Slug"},"content_quality":{"type":"string","title":"Content Quality","default":""},"partnership_history":{"type":"string","title":"Partnership History","default":""},"outreach_strategy":{"items":{"type":"string"},"type":"array","title":"Outreach Strategy","default":[]},"negotiation_notes":{"type":"string","title":"Negotiation Notes","default":""},"competitive_context":{"type":"string","title":"Competitive Context","default":""},"generated_at":{"type":"string","title":"Generated At"},"videos_analyzed":{"type":"integer","title":"Videos Analyzed"},"cost_credits":{"type":"integer","title":"Cost Credits"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["overall_summary","content_themes","audience_signal","partnership_signals","red_flags","fit_for","generated_at","videos_analyzed","cost_credits","cached"],"title":"DeepOverviewResponse","description":"Strict superset of OverviewSummaryResponse — five extra paragraph-\nlevel sections that justify the deep tier's paid price point."},"DemoKOL":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"platform":{"type":"string","title":"Platform"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bio"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"profile_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Url"},"email_masked":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Masked"},"one_liner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"One Liner"},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights","default":[]}},"type":"object","required":["username","platform"],"title":"DemoKOL","description":"Slim, deliberately-masked DTO. Mirrors the public-facing fields\na visitor sees before they sign up. No discovery metadata, no PII\nbeyond a partially-masked email."},"DemoSearchResponse":{"properties":{"keyword":{"type":"string","title":"Keyword"},"results":{"items":{"$ref":"#/components/schemas/DemoKOL"},"type":"array","title":"Results"},"total_available":{"type":"integer","title":"Total Available"},"truncated":{"type":"boolean","title":"Truncated"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["keyword","results","total_available","truncated"],"title":"DemoSearchResponse"},"DetectNowResponse":{"properties":{"api_key_hint":{"type":"string","title":"Api Key Hint","description":"First 8 chars of the api key."},"alerts_generated":{"type":"integer","title":"Alerts Generated"}},"type":"object","required":["api_key_hint","alerts_generated"],"title":"DetectNowResponse"},"DingTalkSendIn":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Supabase user id of the admin"},"markdown_text":{"type":"string","maxLength":8000,"minLength":1,"title":"Markdown Text"},"title":{"type":"string","maxLength":200,"title":"Title","default":"KOLens admin"}},"type":"object","required":["user_id","markdown_text"],"title":"DingTalkSendIn"},"DingTalkSendOut":{"properties":{"ok":{"type":"boolean","title":"Ok"},"status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status"},"errcode":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Errcode"},"errmsg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Errmsg"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"via":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Via"}},"type":"object","required":["ok"],"title":"DingTalkSendOut"},"DirectoryCard":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"type":"string","title":"Platform"},"display_name":{"type":"string","title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"follower_count":{"type":"integer","title":"Follower Count"},"tier":{"type":"string","title":"Tier"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"verified":{"type":"boolean","title":"Verified","default":false},"has_public_page":{"type":"boolean","title":"Has Public Page","default":false}},"type":"object","required":["username","platform","display_name","follower_count","tier"],"title":"DirectoryCard"},"DirectoryResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/DirectoryCard"},"type":"array","title":"Entries"},"total":{"type":"integer","title":"Total"},"truncated":{"type":"boolean","title":"Truncated"}},"type":"object","required":["entries","total","truncated"],"title":"DirectoryResponse","description":"Enriched, paginated, public-safe creator cards for /tiktok-creators (#648)."},"DiscoveryFilters":{"properties":{"min_followers":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Min Followers"},"max_followers":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Max Followers"},"min_avg_views":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Min Avg Views"},"min_engagement_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Min Engagement Rate"},"require_email":{"type":"boolean","title":"Require Email","default":false},"roles_in":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Roles In","description":"Only auto-add authors whose dominant role is in this list. Allowed values: KOL, Seller, Customer, Passenger."}},"type":"object","title":"DiscoveryFilters","description":"Filters applied to scraped KOLs before auto-adding to target_list."},"DiscoveryPlanCreate":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"keyword":{"type":"string","maxLength":200,"minLength":1,"title":"Keyword"},"platform":{"type":"string","title":"Platform","description":"User-facing platform. One of tiktok / instagram / x / douyin. The backend resolves this to an internal data_source for pipeline dispatch.","default":"tiktok"},"interval_hours":{"type":"integer","maximum":720.0,"minimum":1.0,"title":"Interval Hours","default":24},"max_videos_per_run":{"type":"integer","maximum":2000.0,"minimum":1.0,"title":"Max Videos Per Run","default":100},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"ISO-3166 alpha-2 country code (e.g. 'US', 'JP', 'SG'). Copied onto every job the scheduler spawns from this plan; the TikTok scrape then browses as if from that country. Omit / empty = the global (US) default."},"filters":{"anyOf":[{"$ref":"#/components/schemas/DiscoveryFilters"},{"type":"null"}]},"target_list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target List Id"},"status":{"type":"string","enum":["draft","active","disabled"],"title":"Status","description":"draft (saved, NOT scheduled) | active (scheduler picks up) | disabled (paused). Defaults to draft — creating a plan no longer auto-schedules it; the user opts in by hitting Activate on the detail page once they've verified the keyword + filters.","default":"draft"},"data_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Source","description":"DEPRECATED — pass ``platform`` instead. Accepted only for backward compat with pre-#163 callers."}},"type":"object","required":["name","keyword"],"title":"DiscoveryPlanCreate","description":"Issue #163 — the preferred field is now ``platform`` (one of\n``tiktok / instagram / x / douyin``). ``data_source`` is retained\nfor backward compat with pre-#163 callers (CLI, Cowork plugin) but\nemits a deprecation warning server-side and is resolved to a\nplatform via ``discovery_platform_resolver``."},"DiscoveryPlanOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"keyword":{"type":"string","title":"Keyword"},"platform":{"type":"string","title":"Platform"},"interval_hours":{"type":"integer","title":"Interval Hours"},"max_videos_per_run":{"type":"integer","title":"Max Videos Per Run"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"filters":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Filters"},"target_list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target List Id"},"status":{"type":"string","enum":["draft","active","disabled"],"title":"Status"},"effective_status":{"type":"string","enum":["draft","active","disabled","queued","running","failed"],"title":"Effective Status"},"data_source":{"type":"string","title":"Data Source"},"last_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run At"},"next_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Run At"},"last_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Job Id"},"last_job_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Job Status"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"query_plan":{"anyOf":[{"$ref":"#/components/schemas/QueryPlanEcho"},{"type":"null"}]}},"type":"object","required":["id","name","keyword","platform","interval_hours","max_videos_per_run","filters","target_list_id","status","effective_status","data_source","last_run_at","next_run_at","last_job_id","created_at","updated_at"],"title":"DiscoveryPlanOut"},"DiscoveryPlanUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"keyword":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Keyword"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"interval_hours":{"anyOf":[{"type":"integer","maximum":720.0,"minimum":1.0},{"type":"null"}],"title":"Interval Hours"},"max_videos_per_run":{"anyOf":[{"type":"integer","maximum":2000.0,"minimum":1.0},{"type":"null"}],"title":"Max Videos Per Run"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"ISO-3166 alpha-2 country code. Send an empty string to clear the plan's region back to the global default."},"filters":{"anyOf":[{"$ref":"#/components/schemas/DiscoveryFilters"},{"type":"null"}]},"target_list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target List Id"},"status":{"anyOf":[{"type":"string","enum":["draft","active","disabled"]},{"type":"null"}],"title":"Status"},"data_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Source"}},"type":"object","title":"DiscoveryPlanUpdate"},"DiscoveryRunNowResponse":{"properties":{"plan_id":{"type":"string","title":"Plan Id"},"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"}},"type":"object","required":["plan_id","job_id","status"],"title":"DiscoveryRunNowResponse"},"DisputeIn":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note","description":"Optional explanation of why this score is contested."}},"type":"object","title":"DisputeIn"},"DouyinScrapeRequest":{"properties":{"keyword":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Keyword"},"sec_uids":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Sec Uids"},"handles":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Handles"},"max_results":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Max Results","default":50}},"type":"object","title":"DouyinScrapeRequest","description":"Exactly one of ``keyword`` / ``sec_uids`` / ``handles`` must be\nsupplied. ``max_results`` follows the YT semantics for keyword\nmode (videos to pull per creator) and the per-author mode\n(videos to pull per author) — both bounded at 200, which is well\nbelow Apify's 5-minute run-sync cap for the Douyin actors."},"DouyinScrapeResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"keyword":{"type":"string","title":"Keyword"},"estimated_seconds":{"type":"integer","title":"Estimated Seconds"}},"type":"object","required":["job_id","status","keyword","estimated_seconds"],"title":"DouyinScrapeResponse"},"DraftOutreachRequest":{"properties":{"prompt_hint":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Prompt Hint"}},"type":"object","title":"DraftOutreachRequest"},"DraftOutreachResponse":{"properties":{"subject":{"type":"string","title":"Subject"},"body_plain":{"type":"string","title":"Body Plain"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"rationale":{"type":"string","title":"Rationale"},"recommended_send_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommended Send Time"},"tokens_used":{"type":"integer","title":"Tokens Used"},"model":{"type":"string","title":"Model"}},"type":"object","required":["subject","body_plain","rationale","tokens_used","model"],"title":"DraftOutreachResponse"},"EnableMonitoringRequest":{"properties":{"enable":{"type":"boolean","title":"Enable"}},"type":"object","required":["enable"],"title":"EnableMonitoringRequest"},"EnrichContactResponse":{"properties":{"found":{"type":"boolean","title":"Found"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"verified":{"type":"boolean","title":"Verified","default":false},"deliverability_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Deliverability Score"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"cost_credits":{"type":"integer","title":"Cost Credits"},"cached":{"type":"boolean","title":"Cached"},"summary":{"type":"string","title":"Summary"},"next_actions":{"items":{"type":"string"},"type":"array","title":"Next Actions","default":[]}},"type":"object","required":["found","cost_credits","cached","summary"],"title":"EnrichContactResponse"},"EnrichEmailRequest":{"properties":{"email":{"type":"string","maxLength":200,"minLength":3,"title":"Email"}},"type":"object","required":["email"],"title":"EnrichEmailRequest"},"EnrichPostsRequest":{"properties":{"handle":{"type":"string","maxLength":200,"minLength":1,"title":"Handle"}},"type":"object","required":["handle"],"title":"EnrichPostsRequest"},"EnrichRawRequest":{"properties":{"handle":{"type":"string","maxLength":200,"minLength":1,"title":"Handle"}},"type":"object","required":["handle"],"title":"EnrichRawRequest"},"EnrichRequest":{"properties":{"handle":{"type":"string","maxLength":200,"minLength":1,"title":"Handle","description":"TikTok username or profile URL"},"email_required":{"type":"string","title":"Email Required","description":"'must_have' returns only if email found; 'preferred' returns data even without email","default":"preferred"},"include_lookalikes":{"type":"boolean","title":"Include Lookalikes","default":false},"include_audience_data":{"type":"boolean","title":"Include Audience Data","default":true}},"type":"object","required":["handle"],"title":"EnrichRequest"},"EnrichSocialsRequest":{"properties":{"handle":{"type":"string","maxLength":200,"minLength":1,"title":"Handle"}},"type":"object","required":["handle"],"title":"EnrichSocialsRequest"},"EventIn":{"properties":{"event":{"type":"string","maxLength":64,"title":"Event"},"workspace_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Workspace Id"},"props":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Props"}},"type":"object","required":["event"],"title":"EventIn"},"EventOut":{"properties":{"id":{"type":"string","title":"Id"},"conversation_id":{"type":"string","title":"Conversation Id"},"kind":{"type":"string","title":"Kind"},"direction":{"type":"string","title":"Direction"},"body":{"type":"string","title":"Body"},"sent_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent By User Id"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"ai_generated":{"type":"boolean","title":"Ai Generated"},"sender_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Email"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"body_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Text"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Received At"}},"type":"object","required":["id","conversation_id","kind","direction","body","sent_at","ai_generated"],"title":"EventOut"},"FeishuSyncBody":{"properties":{"bitable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bitable Name"},"app_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Token"},"table_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Table Id"}},"type":"object","title":"FeishuSyncBody","description":"Optional body for Feishu sync — omit to auto-create a fresh Bitable."},"FeishuSyncResult":{"properties":{"list_id":{"type":"string","title":"List Id"},"list_name":{"type":"string","title":"List Name"},"bitable_url":{"type":"string","title":"Bitable Url"},"app_token":{"type":"string","title":"App Token"},"table_id":{"type":"string","title":"Table Id"},"synced":{"type":"integer","title":"Synced"}},"type":"object","required":["list_id","list_name","bitable_url","app_token","table_id","synced"],"title":"FeishuSyncResult"},"FitFlagOut":{"properties":{"code":{"type":"string","title":"Code"},"severity":{"type":"string","title":"Severity"},"detail":{"type":"string","title":"Detail"}},"type":"object","required":["code","severity","detail"],"title":"FitFlagOut","description":"One decision signal on a member's row."},"FitOut":{"properties":{"score":{"type":"integer","title":"Score"},"flags":{"items":{"$ref":"#/components/schemas/FitFlagOut"},"type":"array","title":"Flags","default":[]}},"type":"object","required":["score"],"title":"FitOut"},"GSheetsSyncBody":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"share_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share With"}},"type":"object","title":"GSheetsSyncBody","description":"Optional body for Google Sheets sync."},"GSheetsSyncResult":{"properties":{"list_id":{"type":"string","title":"List Id"},"list_name":{"type":"string","title":"List Name"},"spreadsheet_url":{"type":"string","title":"Spreadsheet Url"},"spreadsheet_id":{"type":"string","title":"Spreadsheet Id"},"synced":{"type":"integer","title":"Synced"}},"type":"object","required":["list_id","list_name","spreadsheet_url","spreadsheet_id","synced"],"title":"GSheetsSyncResult"},"GrantRequest":{"properties":{"mode":{"type":"string","title":"Mode","description":"``add`` treats ``credits`` as a delta; ``set`` treats it as the balance to end up with.","default":"add"},"credits":{"type":"number","title":"Credits","description":"In ``add`` mode, credits to add (negative corrects an over-grant). In ``set`` mode, the resulting balance."},"reason":{"type":"string","maxLength":500,"minLength":3,"title":"Reason","description":"Why. Written to the audit log and the ledger row."},"idempotency_key":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Idempotency Key","description":"Same key = same grant. Supply one per form submission so a double-click or a retried request cannot double-credit."}},"type":"object","required":["credits","reason"],"title":"GrantRequest"},"GrowthAlertList":{"properties":{"total":{"type":"integer","title":"Total"},"unread":{"type":"integer","title":"Unread"},"alerts":{"items":{"$ref":"#/components/schemas/GrowthAlertOut"},"type":"array","title":"Alerts"}},"type":"object","required":["total","unread","alerts"],"title":"GrowthAlertList"},"GrowthAlertOut":{"properties":{"id":{"type":"string","title":"Id"},"alert_type":{"type":"string","enum":["rising_kol","trending_video","audience_drift","comment_keyword"],"title":"Alert Type"},"subject_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Username"},"subject_video_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Video Id"},"score":{"type":"number","title":"Score"},"payload":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Payload"},"is_read":{"type":"boolean","title":"Is Read"},"created_at":{"type":"string","title":"Created At"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"trigger":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Trigger"},"identity":{"anyOf":[{"$ref":"#/components/schemas/AlertIdentity"},{"type":"null"}]}},"type":"object","required":["id","alert_type","subject_username","subject_video_id","score","payload","is_read","created_at"],"title":"GrowthAlertOut"},"GrowthTrendsResponse":{"properties":{"username":{"type":"string","title":"Username"},"days":{"type":"integer","title":"Days"},"follower_growth_rate":{"type":"number","title":"Follower Growth Rate"},"avg_views_growth_rate":{"type":"number","title":"Avg Views Growth Rate"},"engagement_rate_change":{"type":"number","title":"Engagement Rate Change"},"snapshots_count":{"type":"integer","title":"Snapshots Count"}},"type":"object","required":["username","days","follower_growth_rate","avg_views_growth_rate","engagement_rate_change","snapshots_count"],"title":"GrowthTrendsResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HandleLookupRequest":{"properties":{"handle":{"type":"string","maxLength":256,"minLength":1,"title":"Handle"},"max_videos":{"type":"integer","maximum":200.0,"minimum":10.0,"title":"Max Videos","default":50}},"type":"object","required":["handle"],"title":"HandleLookupRequest"},"HandleLookupResponse":{"properties":{"username":{"type":"string","title":"Username"},"profile_url":{"type":"string","title":"Profile Url"},"credits_charged":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Charged"},"videos_scraped":{"type":"integer","title":"Videos Scraped"},"account_created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Account Created At"},"first_video_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Video At"},"last_video_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Video At"},"posting_cadence_days":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Posting Cadence Days"},"videos_per_week":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Videos Per Week"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"}},"type":"object","required":["username","profile_url","videos_scraped"],"title":"HandleLookupResponse"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"version":{"type":"string","title":"Version"},"app_name":{"type":"string","title":"App Name"}},"type":"object","required":["status","version","app_name"],"title":"HealthResponse"},"HealthzResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revision"}},"type":"object","required":["ok"],"title":"HealthzResponse","description":"Liveness, plus the one fact you cannot get any other way.\n\n``revision`` is the git SHA baked into the image at build time. It\nexists because \"is the merge live?\" had no answer from outside: the\ndeploy job reporting success proves a build ran and a health check\npassed, not that the machine serving traffic is running the commit\nyou merged. #785 shipped and the only way to check was to infer it.\nThe deploy workflow's own comments record a release that looked\nmerged and was live nowhere for 20 minutes.\n\n``None`` when the image was built without the build-arg — local\nruns, and any older image. Absence means unknown, never \"stale\"."},"ICSearchRequest":{"properties":{"keyword":{"type":"string","title":"Keyword","description":"Search keyword or query","default":""},"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Page Size","default":20},"followers_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers Min"},"followers_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers Max"},"engagement_rate_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate Min","description":"Min ER as decimal, e.g. 0.02 = 2%"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"has_email":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Email"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"refresh":{"type":"boolean","title":"Refresh","description":"Force refresh, bypass cache","default":false},"audience_gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience Gender","description":"female | male"},"audience_gender_min_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Audience Gender Min Pct","description":"Min % of audience with that gender (0-100)"},"audience_location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience Location","description":"Country name, e.g. 'United States'"},"audience_location_min_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Audience Location Min Pct","description":"Min % audience from that country (0-100)"},"audience_age_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Age Min","description":"Min age of primary audience segment"},"audience_age_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Age Max","description":"Max age of primary audience segment"},"audience_interests":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience Interests","description":"Comma-separated interests, e.g. 'beauty,fashion'"}},"type":"object","title":"ICSearchRequest"},"IdentityOut":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"verified":{"type":"boolean","title":"Verified","default":false}},"type":"object","required":["username"],"title":"IdentityOut"},"InsightDetail":{"properties":{"id":{"type":"string","title":"Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"keyword":{"type":"string","title":"Keyword"},"intent":{"type":"string","title":"Intent"},"status":{"type":"string","title":"Status"},"schema_version":{"type":"string","title":"Schema Version","default":"1.0"},"report_json":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Report Json"},"error_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Text"},"llm_tokens_used":{"type":"integer","title":"Llm Tokens Used"},"source_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Job Id"},"generated_at":{"type":"string","title":"Generated At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"stale":{"type":"boolean","title":"Stale","default":false}},"type":"object","required":["id","workspace_id","keyword","intent","status","llm_tokens_used","generated_at"],"title":"InsightDetail"},"InstagramScrapeRequest":{"properties":{"hashtag":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Hashtag"},"usernames":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Usernames"},"max_videos":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Max Videos","default":50}},"type":"object","title":"InstagramScrapeRequest","description":"Either ``hashtag`` or ``usernames`` must be supplied — not both.\n\n``max_videos`` follows the TikTok semantics: the number of recent\nposts to pull per profile (NOT the total across the batch)."},"InstagramScrapeResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"keyword":{"type":"string","title":"Keyword"},"estimated_seconds":{"type":"integer","title":"Estimated Seconds"}},"type":"object","required":["job_id","status","keyword","estimated_seconds"],"title":"InstagramScrapeResponse"},"IntentOption":{"properties":{"action":{"type":"string","enum":["lookup_account","keyword_search"],"title":"Action"},"label":{"type":"string","title":"Label"},"detail":{"type":"string","title":"Detail"},"endpoint":{"type":"string","title":"Endpoint"}},"type":"object","required":["action","label","detail","endpoint"],"title":"IntentOption","description":"One branch of the fork, described the way the UI should offer it.\n\nThere are two, not three. An earlier draft also offered \"find\ncreators talking about this brand\" alongside \"search it as a topic\"\n— but ``POST /api/scrape`` takes no mode flag, so both buttons would\nhave sent byte-identical requests. Two choices that do the same\nthing is not a choice; it is a UI that lies about what the system\ncan do. If a real mentions-only search ships, it earns a third\noption then."},"IntentRequest":{"properties":{"query":{"type":"string","maxLength":300,"minLength":1,"title":"Query"},"platform":{"type":"string","maxLength":20,"title":"Platform","default":"tiktok"}},"type":"object","required":["query"],"title":"IntentRequest"},"IntentResponse":{"properties":{"query":{"type":"string","title":"Query"},"platform":{"type":"string","title":"Platform"},"intent":{"type":"string","title":"Intent"},"needs_disambiguation":{"type":"boolean","title":"Needs Disambiguation"},"options":{"items":{"$ref":"#/components/schemas/IntentOption"},"type":"array","title":"Options","default":[]},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt"}},"type":"object","required":["query","platform","intent","needs_disambiguation"],"title":"IntentResponse"},"InvitationOut":{"properties":{"id":{"type":"string","title":"Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"email":{"type":"string","title":"Email"},"role":{"type":"string","enum":["admin","billing","member"],"title":"Role"},"token":{"type":"string","title":"Token"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","workspace_id","email","role","token","expires_at","created_at"],"title":"InvitationOut"},"InviteRequest":{"properties":{"email":{"type":"string","maxLength":255,"minLength":3,"title":"Email"},"role":{"type":"string","enum":["admin","billing","member"],"title":"Role","default":"member"}},"type":"object","required":["email"],"title":"InviteRequest"},"JobEventResponse":{"properties":{"event_type":{"type":"string","title":"Event Type"},"message":{"type":"string","title":"Message"},"progress":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Progress"},"payload":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Payload"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["event_type","message","progress","payload","created_at"],"title":"JobEventResponse"},"JobListItem":{"properties":{"job_id":{"type":"string","title":"Job Id"},"keyword":{"type":"string","title":"Keyword"},"status":{"type":"string","title":"Status"},"kol_count":{"type":"integer","title":"Kol Count"},"total_videos":{"type":"integer","title":"Total Videos"},"progress":{"type":"integer","title":"Progress"},"created_at":{"type":"string","title":"Created At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"credits_charged":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Charged"}},"type":"object","required":["job_id","keyword","status","kol_count","total_videos","progress","created_at","completed_at"],"title":"JobListItem"},"JobStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"progress":{"type":"integer","title":"Progress"},"keyword":{"type":"string","title":"Keyword"},"total_videos":{"type":"integer","title":"Total Videos"},"kol_count":{"type":"integer","title":"Kol Count"},"seller_count":{"type":"integer","title":"Seller Count"},"created_at":{"type":"string","title":"Created At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"export_ready":{"type":"boolean","title":"Export Ready"},"attempt_count":{"type":"integer","title":"Attempt Count"},"max_attempts":{"type":"integer","title":"Max Attempts"},"cancel_requested":{"type":"boolean","title":"Cancel Requested"},"queue_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Position"},"credits_charged":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Credits Charged"},"unique_kol_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unique Kol Count"},"new_kol_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"New Kol Count"},"owned_kol_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Owned Kol Count"},"track":{"type":"object","title":"Track"}},"type":"object","required":["job_id","status","stage","progress","keyword","total_videos","kol_count","seller_count","created_at","started_at","completed_at","error_message","export_ready","attempt_count","max_attempts","cancel_requested","queue_position","track"],"title":"JobStatusResponse"},"KOLListResponse":{"properties":{"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"},"kols":{"items":{"$ref":"#/components/schemas/KOLSummary"},"type":"array","title":"Kols"},"llm_meta":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Llm Meta"}},"type":"object","required":["total","page","per_page","kols"],"title":"KOLListResponse"},"KOLProfile":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"is_verified":{"type":"boolean","title":"Is Verified"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"view_to_follower_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"View To Follower Ratio"},"has_email":{"type":"boolean","title":"Has Email"},"has_website":{"type":"boolean","title":"Has Website"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"contact":{"anyOf":[{"$ref":"#/components/schemas/ContactSchema"},{"type":"null"}]},"first_seen_at":{"type":"string","title":"First Seen At"},"last_updated_at":{"type":"string","title":"Last Updated At"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"avatar_url_cached":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url Cached"},"keywords_found_in":{"items":{"type":"string"},"type":"array","title":"Keywords Found In"},"audience_data":{"anyOf":[{"$ref":"#/components/schemas/AudienceData"},{"type":"null"}]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"category_inferred":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Inferred"},"category_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Source"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"llm":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Llm"},"display_name_zh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name Zh"},"category_zh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Zh"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bio"},"bio_zh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bio Zh"},"median_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Views"},"max_views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Views"},"avg_likes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Likes"},"total_videos_in_db":{"type":"integer","title":"Total Videos In Db"},"tiktok_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tiktok Id"},"profile_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Url"},"is_private":{"type":"boolean","title":"Is Private","default":false},"total_videos":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Videos"},"total_hearts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Hearts"},"instagram_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instagram Id"},"youtube_channel_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Channel Id"},"x_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X Handle"},"x_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X User Id"},"xhs_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Xhs User Id"},"douyin_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Douyin Id"},"douyin_sec_uid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Douyin Sec Uid"},"account_created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Created At"},"first_video_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Video At"},"last_video_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Video At"},"posting_cadence_days":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Posting Cadence Days"},"videos_per_week":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Videos Per Week"}},"type":"object","required":["username","display_name","follower_count","is_verified","avg_views","avg_engagement_rate","view_to_follower_ratio","has_email","has_website","first_seen_at","last_updated_at","keywords_found_in","bio","median_views","max_views","avg_likes","total_videos_in_db"],"title":"KOLProfile"},"KOLSummary":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"is_verified":{"type":"boolean","title":"Is Verified"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"view_to_follower_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"View To Follower Ratio"},"has_email":{"type":"boolean","title":"Has Email"},"has_website":{"type":"boolean","title":"Has Website"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"contact":{"anyOf":[{"$ref":"#/components/schemas/ContactSchema"},{"type":"null"}]},"first_seen_at":{"type":"string","title":"First Seen At"},"last_updated_at":{"type":"string","title":"Last Updated At"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"avatar_url_cached":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url Cached"},"keywords_found_in":{"items":{"type":"string"},"type":"array","title":"Keywords Found In","default":[]},"audience_data":{"anyOf":[{"$ref":"#/components/schemas/AudienceData"},{"type":"null"}]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"category_inferred":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Inferred"},"category_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Source"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"llm":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Llm"},"display_name_zh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name Zh"},"category_zh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Zh"}},"type":"object","required":["username","display_name","follower_count","is_verified","avg_views","avg_engagement_rate","view_to_follower_ratio","has_email","has_website","first_seen_at","last_updated_at"],"title":"KOLSummary"},"KeywordsRequest":{"properties":{"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords"}},"type":"object","title":"KeywordsRequest"},"LedgerEntry":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","enum":["debit","credit"],"title":"Kind"},"delta":{"type":"number","title":"Delta"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"label":{"type":"string","title":"Label","default":""},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"data_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Source"},"source":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Source"},"amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amount Cents"},"stripe_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Session Id"}},"type":"object","required":["id","kind","delta"],"title":"LedgerEntry","description":"One row in the unified credit history.\n\n``kind`` discriminates the two event sources:\n  - ``\"debit\"``  — a scrape job that charged credits (from search_jobs).\n  - ``\"credit\"`` — a credit top-up / purchase (from credit_purchases).\n\n``delta`` is signed: negative for debits, positive for credits, so a\nclient can render \"−5\" / \"+200\" without re-deriving the sign from\n``kind``. ``created_at`` is the ISO timestamp the event is sorted by."},"LedgerOut":{"properties":{"user_id":{"type":"string","title":"User Id"},"limit":{"type":"integer","title":"Limit"},"entries":{"items":{"$ref":"#/components/schemas/LedgerEntry"},"type":"array","title":"Entries"}},"type":"object","required":["user_id","limit"],"title":"LedgerOut"},"ListCreate":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["name"],"title":"ListCreate"},"ListDecisionRollup":{"properties":{"total_members":{"type":"integer","title":"Total Members","default":0},"n_untriaged":{"type":"integer","title":"N Untriaged","default":0},"n_shortlisted":{"type":"integer","title":"N Shortlisted","default":0},"n_passed":{"type":"integer","title":"N Passed","default":0},"n_pursuing":{"type":"integer","title":"N Pursuing","default":0},"n_red_flag":{"type":"integer","title":"N Red Flag","default":0},"n_missing_contact":{"type":"integer","title":"N Missing Contact","default":0},"total_estimated_price_min":{"type":"number","title":"Total Estimated Price Min","default":0.0},"total_estimated_price_max":{"type":"number","title":"Total Estimated Price Max","default":0.0}},"type":"object","title":"ListDecisionRollup","description":"Top-level decision rollup — answers \"what does this batch look\nlike?\" so the detail UI renders a header without scanning rows."},"ListIndexRollup":{"properties":{"member_count":{"type":"integer","title":"Member Count","default":0},"n_shortlisted":{"type":"integer","title":"N Shortlisted","default":0},"n_untriaged":{"type":"integer","title":"N Untriaged","default":0},"n_passed":{"type":"integer","title":"N Passed","default":0},"n_red_flag":{"type":"integer","title":"N Red Flag","default":0}},"type":"object","title":"ListIndexRollup","description":"Compact decision rollup carried by each list in ``GET /api/lists``\nso the portfolio view can show \"Skincare Q3 · 40 KOL · 8 shortlisted\n· 12 untriaged · 6 red-flag\" without a per-list detail call."},"ListOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"member_count":{"type":"integer","title":"Member Count"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"enable_daily_monitoring":{"type":"boolean","title":"Enable Daily Monitoring","default":true},"rollup":{"$ref":"#/components/schemas/ListIndexRollup"}},"type":"object","required":["id","name","description","member_count","created_at","updated_at"],"title":"ListOut"},"ListUpdate":{"properties":{"enable_daily_monitoring":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enable Daily Monitoring"}},"type":"object","title":"ListUpdate","description":"PATCH shape for ``PATCH /api/lists/{id}``.\n\nIssue #391 — the only mutable field on a list (so far) is\n``enable_daily_monitoring``, the daily-snapshot opt-out toggle the\nlist-detail UI exposes in its header. All fields optional so partial\nupdates work; the route only applies the keys the caller sent.\nAdding name/description here later is a non-breaking change."},"MarkReadResponse":{"properties":{"id":{"type":"string","title":"Id"},"is_read":{"type":"boolean","title":"Is Read"}},"type":"object","required":["id","is_read"],"title":"MarkReadResponse"},"MarkSeenRequest":{"properties":{"event_ids":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":500},{"type":"null"}],"title":"Event Ids"},"all_before":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"All Before"}},"type":"object","title":"MarkSeenRequest","description":"Bulk mark-seen payload. Exactly one of ``event_ids`` /\n``all_before`` should be supplied; passing both raises 400."},"MarkUnreliableReadRequest":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","description":"When true, count what would be marked read but commit nothing.","default":false}},"type":"object","title":"MarkUnreliableReadRequest","description":"Optional body for the cleanup sweep. ``dry_run`` lets the operator\npreview what WOULD be marked without committing anything."},"MarkUnreliableReadResponse":{"properties":{"scanned":{"type":"integer","title":"Scanned","description":"Unread rising_kol rows examined."},"marked_read":{"type":"integer","title":"Marked Read","description":"Rows marked read (or that WOULD be, when dry_run)."},"dry_run":{"type":"boolean","title":"Dry Run"}},"type":"object","required":["scanned","marked_read","dry_run"],"title":"MarkUnreliableReadResponse"},"MemberActivityListOut":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"slug":{"type":"string","title":"Slug"},"since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Since"},"rows":{"items":{"$ref":"#/components/schemas/MemberActivityOut"},"type":"array","title":"Rows"}},"type":"object","required":["workspace_id","slug","rows"],"title":"MemberActivityListOut"},"MemberActivityOut":{"properties":{"user_id":{"type":"string","title":"User Id"},"role":{"type":"string","enum":["admin","billing","member"],"title":"Role"},"joined_at":{"type":"string","format":"date-time","title":"Joined At"},"last_active_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Active At"},"search_count":{"type":"integer","title":"Search Count","default":0},"watch_count":{"type":"integer","title":"Watch Count","default":0},"scrape_count":{"type":"integer","title":"Scrape Count","default":0},"audience_refresh_count":{"type":"integer","title":"Audience Refresh Count","default":0},"credits_consumed":{"type":"number","title":"Credits Consumed","default":0.0}},"type":"object","required":["user_id","role","joined_at"],"title":"MemberActivityOut","description":"One row of the workspace's \"member activity\" table.\n\nAll counters are integers except ``credits_consumed`` (float —\n``search_jobs.credits_charged`` is FLOAT in the schema). The route\nlayer scopes time-bounded counters to a caller-supplied ``since``\nwindow; ``watch_count`` is always the current cumulative count."},"MemberContactOut":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"instagram":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instagram"},"youtube":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube"},"linktree_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linktree Url"},"has_email":{"type":"boolean","title":"Has Email","default":false},"has_website":{"type":"boolean","title":"Has Website","default":false},"is_complete":{"type":"boolean","title":"Is Complete","default":false}},"type":"object","title":"MemberContactOut"},"MemberDecisionOut":{"properties":{"username":{"type":"string","title":"Username"},"added_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Added At"},"identity":{"$ref":"#/components/schemas/MemberIdentityOut"},"stats":{"$ref":"#/components/schemas/MemberStatsOut"},"fit":{"$ref":"#/components/schemas/FitOut"},"trend":{"$ref":"#/components/schemas/MemberTrendOut"},"pricing":{"$ref":"#/components/schemas/MemberPricingOut"},"contact":{"$ref":"#/components/schemas/MemberContactOut"},"triage_state":{"type":"string","title":"Triage State"},"pursuing":{"type":"boolean","title":"Pursuing","default":false},"outreach":{"anyOf":[{"$ref":"#/components/schemas/MemberOutreachOut"},{"type":"null"}]},"tags":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tags"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["username","identity","stats","fit","trend","pricing","contact","triage_state"],"title":"MemberDecisionOut","description":"One list member, fully decision-oriented — no per-row follow-up call."},"MemberIdentityOut":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"verified":{"type":"boolean","title":"Verified","default":false},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"}},"type":"object","required":["username"],"title":"MemberIdentityOut"},"MemberListOut":{"properties":{"list_id":{"type":"string","title":"List Id"},"list_name":{"type":"string","title":"List Name"},"rollup":{"$ref":"#/components/schemas/ListDecisionRollup"},"entries":{"items":{"$ref":"#/components/schemas/MemberDecisionOut"},"type":"array","title":"Entries"}},"type":"object","required":["list_id","list_name","rollup","entries"],"title":"MemberListOut","description":"Response for ``GET /api/lists/{id}/members`` — the decision surface."},"MemberOut":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"role":{"type":"string","enum":["admin","billing","member"],"title":"Role"},"invited_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invited By User Id"},"joined_at":{"type":"string","format":"date-time","title":"Joined At"}},"type":"object","required":["id","user_id","role","joined_at"],"title":"MemberOut"},"MemberOutreachOut":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"status":{"type":"string","title":"Status"},"last_event_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Event At"}},"type":"object","required":["conversation_id","status"],"title":"MemberOutreachOut","description":"CRM-derived outreach status. Null on a member with no\n``DBKolConversation``. The list never stores this — it is the CRM's."},"MemberPricingOut":{"properties":{"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier"},"estimated_price_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Estimated Price Min"},"estimated_price_max":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Estimated Price Max"}},"type":"object","title":"MemberPricingOut"},"MemberStatsOut":{"properties":{"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"authenticity_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Authenticity Score"},"recommendation_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recommendation Score"}},"type":"object","title":"MemberStatsOut"},"MemberTrendOut":{"properties":{"follower_delta":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Delta"},"follower_delta_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Follower Delta Pct"},"er_delta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Er Delta"},"trend_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trend Label"}},"type":"object","title":"MemberTrendOut","description":"Growth trend from the two most-recent ``KOLSnapshot`` rows."},"MemberUpdate":{"properties":{"triage_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Triage State"},"tags":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tags"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"MemberUpdate","description":"PATCH shape for ``PATCH /api/lists/{id}/members/{username}``.\n\n``triage_state`` is the list-local decision verdict — one of\n``untriaged`` | ``shortlisted`` | ``passed``. Outreach funnel status\nis NOT settable here any more: it lives in the CRM\n(``DBKolConversation``). All fields optional so the UI can flip just\nthe triage state without re-sending tags/notes."},"ModashAISearchRequest":{"properties":{"query":{"type":"string","maxLength":8192,"minLength":1,"title":"Query","description":"Natural language query"},"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Page Size","default":10},"followers_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers Min"},"followers_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers Max"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"engagement_rate_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate Min"},"has_email":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Email"},"last_posted_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Posted Days"},"account_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Type","description":"creator or brand"},"refresh":{"type":"boolean","title":"Refresh","description":"Bypass cache","default":false}},"type":"object","required":["query"],"title":"ModashAISearchRequest"},"ModashCreditsResponse":{"properties":{"credits":{"type":"number","title":"Credits"},"raw_requests":{"type":"integer","title":"Raw Requests"},"discovery_ratelimit":{"type":"integer","title":"Discovery Ratelimit"},"raw_ratelimit":{"type":"integer","title":"Raw Ratelimit"}},"type":"object","required":["credits","raw_requests","discovery_ratelimit","raw_ratelimit"],"title":"ModashCreditsResponse"},"ModashKOLResult":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"type":"string","title":"Display Name"},"avatar_url":{"type":"string","title":"Avatar Url"},"follower_count":{"type":"integer","title":"Follower Count"},"engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate"},"category":{"type":"string","title":"Category"},"country":{"type":"string","title":"Country"},"gender":{"type":"string","title":"Gender"},"modash_user_id":{"type":"string","title":"Modash User Id"},"email":{"type":"string","title":"Email","default":""},"matched_posts":{"items":{"type":"object"},"type":"array","title":"Matched Posts","default":[]}},"type":"object","required":["username","display_name","avatar_url","follower_count","engagement_rate","category","country","gender","modash_user_id"],"title":"ModashKOLResult"},"ModashSearchRequest":{"properties":{"keyword":{"type":"string","title":"Keyword","description":"Search keyword (used as hashtag relevance filter)","default":""},"page":{"type":"integer","minimum":0.0,"title":"Page","default":0},"page_size":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Page Size","default":15},"sort_field":{"type":"string","title":"Sort Field","description":"Sort field: followers, engagementRate, engagements, views","default":"followers"},"sort_direction":{"type":"string","title":"Sort Direction","description":"asc or desc","default":"desc"},"followers_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers Min"},"followers_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Followers Max"},"engagement_rate_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate Min","description":"Min ER as decimal, e.g. 0.02 = 2%"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender","description":"MALE, FEMALE, KNOWN, UNKNOWN"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Language code, e.g. en"},"location_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Location Ids","description":"Modash location IDs"},"has_email":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Email"},"is_verified":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Verified"},"last_posted_days":{"anyOf":[{"type":"integer","minimum":30.0},{"type":"null"}],"title":"Last Posted Days","description":"Max days since last post"},"audience_location_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Audience Location Ids"},"audience_gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience Gender"},"audience_age_range_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Age Range Min"},"audience_age_range_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Age Range Max"},"refresh":{"type":"boolean","title":"Refresh","description":"Bypass cache","default":false}},"type":"object","title":"ModashSearchRequest"},"ModashSearchResponse":{"properties":{"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"profiles":{"items":{"$ref":"#/components/schemas/ModashKOLResult"},"type":"array","title":"Profiles"},"data_source":{"type":"string","title":"Data Source","default":"modash"}},"type":"object","required":["total","page","profiles"],"title":"ModashSearchResponse"},"MonitoringStatusResponse":{"properties":{"list_id":{"type":"string","title":"List Id"},"list_name":{"type":"string","title":"List Name"},"enable_daily_monitoring":{"type":"boolean","title":"Enable Daily Monitoring"},"last_snapshot_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Snapshot At"}},"type":"object","required":["list_id","list_name","enable_daily_monitoring","last_snapshot_at"],"title":"MonitoringStatusResponse"},"NextActionResponse":{"properties":{"recommendation":{"type":"string","title":"Recommendation"},"rationale":{"type":"string","title":"Rationale"},"tokens_used":{"type":"integer","title":"Tokens Used"},"model":{"type":"string","title":"Model"}},"type":"object","required":["recommendation","rationale","tokens_used","model"],"title":"NextActionResponse"},"NotionExportRequest":{"properties":{"notion_token":{"type":"string","maxLength":200,"minLength":10,"title":"Notion Token"},"database_id":{"type":"string","maxLength":64,"minLength":10,"title":"Database Id"}},"type":"object","required":["notion_token","database_id"],"title":"NotionExportRequest"},"NotionExportResponse":{"properties":{"notion_page_id":{"type":"string","title":"Notion Page Id"},"url":{"type":"string","title":"Url"}},"type":"object","required":["notion_page_id","url"],"title":"NotionExportResponse"},"OutreachBriefOut":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"status":{"type":"string","title":"Status"},"last_event_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Event At"},"days_since_last_event":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Since Last Event"},"event_count":{"type":"integer","title":"Event Count","default":0}},"type":"object","required":["conversation_id","status"],"title":"OutreachBriefOut"},"OutreachDraftResponse":{"properties":{"dm":{"type":"string","title":"Dm"},"email_subject":{"type":"string","title":"Email Subject"},"email_body":{"type":"string","title":"Email Body"},"generated_at":{"type":"string","title":"Generated At"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["dm","email_subject","email_body","generated_at","cached"],"title":"OutreachDraftResponse"},"OutreachEvent":{"properties":{"kind":{"type":"string","title":"Kind"},"at":{"type":"string","title":"At"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"body_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Preview"},"sendgrid_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sendgrid Message Id"}},"type":"object","required":["kind","at"],"title":"OutreachEvent","description":"One event on this KOL's outreach timeline."},"OutreachHistoryResponse":{"properties":{"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"last_event_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Event At"},"event_count":{"type":"integer","title":"Event Count","default":0},"events":{"items":{"$ref":"#/components/schemas/OutreachEvent"},"type":"array","title":"Events","default":[]},"summary":{"type":"string","title":"Summary","default":""}},"type":"object","title":"OutreachHistoryResponse"},"OutreachRewriteRequest":{"properties":{"instruction":{"type":"string","title":"Instruction"}},"type":"object","required":["instruction"],"title":"OutreachRewriteRequest"},"OutreachTranslateRequest":{"properties":{"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"},"target_locale":{"type":"string","title":"Target Locale","default":"zh-CN"}},"type":"object","required":["subject","body"],"title":"OutreachTranslateRequest"},"OutreachTranslateResponse":{"properties":{"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"},"target_locale":{"type":"string","title":"Target Locale"}},"type":"object","required":["subject","body","target_locale"],"title":"OutreachTranslateResponse"},"OverviewSummaryResponse":{"properties":{"overall_summary":{"type":"string","title":"Overall Summary"},"content_themes":{"items":{"type":"string"},"type":"array","title":"Content Themes"},"audience_signal":{"type":"string","title":"Audience Signal"},"partnership_signals":{"items":{"type":"string"},"type":"array","title":"Partnership Signals"},"red_flags":{"items":{"type":"string"},"type":"array","title":"Red Flags"},"fit_for":{"items":{"type":"string"},"type":"array","title":"Fit For"},"recommendation_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recommendation Score"},"content_fit_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Content Fit Score"},"audience_fit_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Fit Score"},"risk_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Risk Score"},"score_rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Score Rationale"},"brand_profile_status":{"type":"string","title":"Brand Profile Status","default":"missing"},"workspace_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Slug"},"generated_at":{"type":"string","title":"Generated At"},"videos_analyzed":{"type":"integer","title":"Videos Analyzed"},"cost_credits":{"type":"integer","title":"Cost Credits"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["overall_summary","content_themes","audience_signal","partnership_signals","red_flags","fit_for","generated_at","videos_analyzed","cost_credits","cached"],"title":"OverviewSummaryResponse"},"ParseRequest":{"properties":{"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform","description":"tiktok | instagram | x | douyin"}},"type":"object","required":["query"],"title":"ParseRequest"},"ParseResponse":{"properties":{"keyword":{"type":"string","title":"Keyword","description":"Short scrapable term to run"},"filters":{"type":"object","title":"Filters","description":"Keys are /api/kols query params — forward as-is"},"parsed":{"type":"boolean","title":"Parsed","description":"False = we could not improve on the input; keyword is unchanged"},"notes":{"type":"string","title":"Notes","description":"What we understood, in the user's language","default":""},"original":{"type":"string","title":"Original","description":"The brief exactly as submitted","default":""},"needs_topic":{"type":"boolean","title":"Needs Topic","description":"Conditions understood but no topic named — ask the user for one. Running the brief as-is would be a guaranteed zero-result charge.","default":false}},"type":"object","required":["keyword","parsed"],"title":"ParseResponse"},"PlacementIn":{"properties":{"kol_username":{"type":"string","maxLength":120,"minLength":1,"title":"Kol Username"},"kol_platform":{"type":"string","maxLength":20,"title":"Kol Platform","default":"tiktok"},"amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Amount"},"currency":{"anyOf":[{"type":"string","maxLength":3,"minLength":3},{"type":"null"}],"title":"Currency"},"fx_rate_to_campaign":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Fx Rate To Campaign","description":"Rate to the campaign's currency, frozen at entry. Required only when the placement currency differs."},"status":{"type":"string","title":"Status","default":"planned"},"paid_on":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid On"},"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note"}},"type":"object","required":["kol_username"],"title":"PlacementIn"},"PlanControl":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"interval_hours":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Interval Hours"},"max_videos_per_run":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Videos Per Run"}},"type":"object","title":"PlanControl"},"PlatformPresence":{"properties":{"platform":{"type":"string","title":"Platform"},"username":{"type":"string","title":"Username"},"in_db":{"type":"boolean","title":"In Db"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"profile_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Url"},"source":{"type":"string","title":"Source"}},"type":"object","required":["platform","username","in_db","source"],"title":"PlatformPresence"},"PlatformPresenceOut":{"properties":{"platform":{"type":"string","title":"Platform"},"username":{"type":"string","title":"Username"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"profile_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Url"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"is_verified":{"type":"boolean","title":"Is Verified","default":false},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},"type":"object","required":["platform","username"],"title":"PlatformPresenceOut"},"PortalCustomerOut":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"slug":{"type":"string","title":"Slug"},"stripe_customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Customer Id"}},"type":"object","required":["workspace_id","slug"],"title":"PortalCustomerOut","description":"Role-gated handshake the Vercel BFF uses to create a Stripe\nCustomer Portal session.\n\nThe Railway layer does NOT call Stripe itself — the Stripe SDK lives\nonly on Vercel (alongside the existing checkout + webhook flows).\nRailway's job here is purely:\n  1. Verify the caller's role (admin / billing — members are 403'd).\n  2. Return the workspace's persisted ``stripe_customer_id`` so the\n     Vercel route can pass it into ``stripe.billingPortal.sessions\n     .create``.\n\nIf no customer has been minted yet (workspace never ran a checkout),\n``stripe_customer_id`` is null. The Vercel route surfaces a clean\n\"Run a top-up first\" error instead of opening the portal."},"ProvenanceResponse":{"properties":{"first_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen At"},"discovery_count":{"type":"integer","title":"Discovery Count","default":0},"sources":{"items":{"$ref":"#/components/schemas/ProvenanceSource"},"type":"array","title":"Sources","default":[]},"summary":{"type":"string","title":"Summary","default":""}},"type":"object","title":"ProvenanceResponse"},"ProvenanceSource":{"properties":{"kind":{"type":"string","title":"Kind"},"keyword":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keyword"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"plan_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Id"},"plan_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Name"},"discovered_at":{"type":"string","title":"Discovered At"},"videos_in_job":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos In Job"}},"type":"object","required":["kind","discovered_at"],"title":"ProvenanceSource","description":"One row in the dossier's \"how was this KOL discovered\" timeline."},"PublicAudienceOut":{"properties":{"top_countries":{"items":{"type":"object"},"type":"array","title":"Top Countries","default":[]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","title":"PublicAudienceOut"},"PublicAuthenticityOut":{"properties":{"score":{"type":"integer","title":"Score"},"confidence":{"type":"string","title":"Confidence"},"tier_label":{"type":"string","title":"Tier Label"}},"type":"object","required":["score","confidence","tier_label"],"title":"PublicAuthenticityOut"},"PublicDossierOut":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"type":"string","title":"Platform"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bio"},"profile_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Url"},"follower_count":{"type":"integer","title":"Follower Count"},"posts_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Posts Count"},"total_hearts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Hearts"},"verified":{"type":"boolean","title":"Verified","default":false},"role":{"type":"string","title":"Role"},"audience":{"$ref":"#/components/schemas/PublicAudienceOut"},"account_created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Created At"},"first_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen At"},"last_video_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Video At"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"recent_videos":{"items":{"$ref":"#/components/schemas/PublicVideoOut"},"type":"array","title":"Recent Videos","default":[]},"authenticity":{"anyOf":[{"$ref":"#/components/schemas/PublicAuthenticityOut"},{"type":"null"}]},"computed_at":{"type":"string","title":"Computed At"},"public_threshold":{"type":"integer","title":"Public Threshold","default":100000}},"type":"object","required":["username","platform","follower_count","role","audience","computed_at"],"title":"PublicDossierOut","description":"Pydantic mirror of ``kol_public_page.PublicDossier``. Keeping\nthese two definitions in lock-step is the privacy contract — any\nfield that appears here MUST also appear in the dataclass."},"PublicVideoOut":{"properties":{"video_id":{"type":"string","title":"Video Id"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption"},"view_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"View Count"},"like_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Like Count"},"comment_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comment Count"},"posted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Posted At"}},"type":"object","required":["video_id"],"title":"PublicVideoOut"},"PurchaseOut":{"properties":{"id":{"type":"string","title":"Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"stripe_session_id":{"type":"string","title":"Stripe Session Id"},"credits_purchased":{"type":"number","title":"Credits Purchased"},"amount_cents":{"type":"integer","title":"Amount Cents"},"status":{"type":"string","title":"Status"},"receipt_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Receipt Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","workspace_id","stripe_session_id","credits_purchased","amount_cents","status","created_at"],"title":"PurchaseOut"},"QueryPlanEcho":{"properties":{"raw_query":{"type":"string","title":"Raw Query"},"search_terms":{"items":{"type":"string"},"type":"array","title":"Search Terms","default":[]},"filters":{"type":"object","title":"Filters","default":{}},"market":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"notes":{"type":"string","title":"Notes","default":""}},"type":"object","required":["raw_query"],"title":"QueryPlanEcho","description":"Display-only echo of how the NL query planner interpreted a typed\nintent sentence (Phase 1). Returned on the create / search-submit\nresponse so the UI can show \"Interpreted as: [terms] · filters\".\n\nOnly present when the planner flag is ON *and* the planner produced a\nreal (non-passthrough) interpretation — null otherwise so existing\nclients ignore it. An editable confirm step is Phase 2."},"RankingBoardOut":{"properties":{"country":{"type":"string","title":"Country"},"window":{"type":"string","title":"Window"},"total":{"type":"integer","title":"Total"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"free_rows":{"type":"integer","title":"Free Rows"},"contactable":{"type":"integer","title":"Contactable"},"members":{"items":{"$ref":"#/components/schemas/RankingMemberOut"},"type":"array","title":"Members"}},"type":"object","required":["country","window","total","free_rows","contactable","members"],"title":"RankingBoardOut"},"RankingMemberOut":{"properties":{"rank":{"type":"integer","title":"Rank"},"username":{"type":"string","title":"Username"},"display_name":{"type":"string","title":"Display Name"},"profile_url":{"type":"string","title":"Profile Url"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"growth_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Growth Rate"},"engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate"},"sampled_videos":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sampled Videos"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"verified":{"type":"boolean","title":"Verified","default":false},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"has_contact":{"type":"boolean","title":"Has Contact","default":false},"in_index":{"type":"boolean","title":"In Index","default":false}},"type":"object","required":["rank","username","display_name","profile_url"],"title":"RankingMemberOut"},"RegenerateRequest":{"properties":{"section":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Section"}},"type":"object","title":"RegenerateRequest"},"RegisterIn":{"properties":{"client_name":{"type":"string","maxLength":120,"minLength":1,"title":"Client Name"},"redirect_uris":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Redirect Uris"},"token_endpoint_auth_method":{"type":"string","title":"Token Endpoint Auth Method","default":"none"},"scope":{"type":"string","title":"Scope","default":"read:kols write:lists run:scrape"}},"type":"object","required":["client_name","redirect_uris"],"title":"RegisterIn"},"ScrapeRequest":{"properties":{"keyword":{"type":"string","maxLength":200,"minLength":1,"title":"Keyword","description":"TikTok search keyword"},"max_videos":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Max Videos","default":100},"max_scrolls":{"type":"integer","maximum":50.0,"minimum":0.0,"title":"Max Scrolls","description":"Max scroll iterations. 0 = auto (calculated from max_videos).","default":0},"fetch_contacts":{"type":"boolean","title":"Fetch Contacts","default":true},"fetch_account_stats":{"type":"boolean","title":"Fetch Account Stats","default":true},"scraper_mode":{"type":"string","title":"Scraper Mode","description":"Scraper browser backend: 'local' or 'cloud' (BrowserBase). Cloud falls back to local automatically if BB is not configured.","default":"cloud"},"data_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Source","description":"Where to source data from: 'scraper' (default, Playwright-based) or 'apify' (Apify TikTok actor, webhook-driven). When unset the server picks the default for the deployment."}},"type":"object","required":["keyword"],"title":"ScrapeRequest"},"ScrapeResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"keyword":{"type":"string","title":"Keyword"},"estimated_seconds":{"type":"integer","title":"Estimated Seconds"},"query_plan":{"anyOf":[{"$ref":"#/components/schemas/QueryPlanEcho"},{"type":"null"}]}},"type":"object","required":["job_id","status","keyword","estimated_seconds"],"title":"ScrapeResponse"},"SearchHistoryCreate":{"properties":{"keyword":{"type":"string","minLength":1,"title":"Keyword"},"data_source":{"type":"string","title":"Data Source","default":"scraper"},"result_count":{"type":"integer","title":"Result Count","default":0}},"type":"object","required":["keyword"],"title":"SearchHistoryCreate"},"SendEmailRequest":{"properties":{"to_addr":{"type":"string","maxLength":320,"minLength":3,"title":"To Addr"},"subject":{"type":"string","maxLength":998,"minLength":1,"title":"Subject"},"body_text":{"type":"string","maxLength":200000,"title":"Body Text"},"body_html":{"anyOf":[{"type":"string","maxLength":400000},{"type":"null"}],"title":"Body Html"}},"type":"object","required":["to_addr","subject","body_text"],"title":"SendEmailRequest"},"SendEmailResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"conversation_id":{"type":"string","title":"Conversation Id"},"reply_to":{"type":"string","title":"Reply To"}},"type":"object","required":["event_id","conversation_id","reply_to"],"title":"SendEmailResponse"},"SendOutreachByKolRequest":{"properties":{"to_addr":{"type":"string","maxLength":320,"minLength":3,"title":"To Addr"},"subject":{"type":"string","maxLength":998,"minLength":1,"title":"Subject"},"body_text":{"type":"string","maxLength":200000,"minLength":1,"title":"Body Text"},"body_html":{"anyOf":[{"type":"string","maxLength":400000},{"type":"null"}],"title":"Body Html"},"kol_platform":{"type":"string","maxLength":20,"title":"Kol Platform","default":"tiktok"}},"type":"object","required":["to_addr","subject","body_text"],"title":"SendOutreachByKolRequest"},"SendOutreachByKolResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"conversation_id":{"type":"string","title":"Conversation Id"},"reply_to":{"type":"string","title":"Reply To"},"is_new_conversation":{"type":"boolean","title":"Is New Conversation"}},"type":"object","required":["event_id","conversation_id","reply_to","is_new_conversation"],"title":"SendOutreachByKolResponse"},"SendOutreachRequest":{"properties":{"to_email":{"type":"string","maxLength":254,"minLength":5,"title":"To Email"},"subject":{"type":"string","maxLength":200,"minLength":1,"title":"Subject"},"body_text":{"type":"string","maxLength":20000,"minLength":1,"title":"Body Text"},"body_html":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}],"title":"Body Html"}},"type":"object","required":["to_email","subject","body_text"],"title":"SendOutreachRequest"},"SendOutreachResponse":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"event_id":{"type":"string","title":"Event Id"},"to_email":{"type":"string","title":"To Email"},"reply_to_address":{"type":"string","title":"Reply To Address"},"workspace_id":{"type":"string","title":"Workspace Id"},"note":{"type":"string","title":"Note"}},"type":"object","required":["conversation_id","event_id","to_email","reply_to_address","workspace_id","note"],"title":"SendOutreachResponse"},"SetPortalCustomerRequest":{"properties":{"stripe_customer_id":{"type":"string","maxLength":64,"minLength":4,"title":"Stripe Customer Id"}},"type":"object","required":["stripe_customer_id"],"title":"SetPortalCustomerRequest","description":"Admin-secret-gated callback. Stamps ``workspaces.stripe_customer_id``\nafter Vercel creates a customer via the first checkout. Idempotent —\nre-setting to the same id is a no-op; changing to a different id\noverwrites + audits the change."},"SignalDetailOut":{"properties":{"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"weight":{"type":"number","title":"Weight"},"note":{"type":"string","title":"Note"}},"type":"object","required":["weight","note"],"title":"SignalDetailOut","description":"One sub-signal's payload — score + weight + 1-line human note."},"SignalOut":{"properties":{"kind":{"type":"string","title":"Kind"},"severity":{"type":"string","title":"Severity"},"label":{"type":"string","title":"Label"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"data":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Data"}},"type":"object","required":["kind","severity","label"],"title":"SignalOut","description":"One badge worth of \"something changed since last check\"."},"SignificanceOut":{"properties":{"score":{"type":"integer","title":"Score"},"tier":{"type":"string","title":"Tier"},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons","default":[]}},"type":"object","required":["score","tier"],"title":"SignificanceOut"},"SimilarCreatorOut":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"country_inferred":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Inferred"},"is_seller_like":{"type":"boolean","title":"Is Seller Like","default":false},"profile_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Url"},"co_mention_count":{"type":"integer","title":"Co Mention Count","default":0},"same_category":{"type":"boolean","title":"Same Category","default":false},"shared_hashtag_count":{"type":"integer","title":"Shared Hashtag Count","default":0},"score":{"type":"integer","title":"Score","default":0}},"type":"object","required":["username"],"title":"SimilarCreatorOut"},"SimilarCreatorsResponse":{"properties":{"seed":{"type":"string","title":"Seed"},"candidates":{"items":{"$ref":"#/components/schemas/SimilarCreatorOut"},"type":"array","title":"Candidates"}},"type":"object","required":["seed","candidates"],"title":"SimilarCreatorsResponse"},"SimilarKOLOut":{"properties":{"username":{"type":"string","title":"Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"follower_count":{"type":"integer","title":"Follower Count"},"verified":{"type":"boolean","title":"Verified","default":false},"role":{"type":"string","title":"Role"},"platform":{"type":"string","title":"Platform"}},"type":"object","required":["username","follower_count","role","platform"],"title":"SimilarKOLOut","description":"Pydantic mirror of ``kol_public_page.SimilarKOL``. Each row is a\npublicly-displayable neighbour creator for the Related Profiles\nsection on ``/k/{username}``."},"SimilarKOLsResponse":{"properties":{"similar":{"items":{"$ref":"#/components/schemas/SimilarKOLOut"},"type":"array","title":"Similar"},"computed_at":{"type":"string","title":"Computed At"}},"type":"object","required":["similar","computed_at"],"title":"SimilarKOLsResponse"},"SimulateInboundRequest":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"from_email":{"type":"string","maxLength":254,"minLength":5,"title":"From Email"},"subject":{"type":"string","maxLength":200,"minLength":1,"title":"Subject"},"body_text":{"type":"string","maxLength":20000,"minLength":1,"title":"Body Text"},"body_html":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}],"title":"Body Html"}},"type":"object","required":["conversation_id","from_email","subject","body_text"],"title":"SimulateInboundRequest"},"SimulateInboundResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"conversation_id":{"type":"string","title":"Conversation Id"},"status_after":{"type":"string","title":"Status After"},"note":{"type":"string","title":"Note"}},"type":"object","required":["event_id","conversation_id","status_after","note"],"title":"SimulateInboundResponse"},"SitemapEntry":{"properties":{"username":{"type":"string","title":"Username"},"platform":{"type":"string","title":"Platform"},"last_updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Updated At"}},"type":"object","required":["username","platform"],"title":"SitemapEntry"},"SitemapResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/SitemapEntry"},"type":"array","title":"Entries"},"total":{"type":"integer","title":"Total"},"truncated":{"type":"boolean","title":"Truncated"},"tiers":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Tiers"}},"type":"object","required":["entries","total","truncated"],"title":"SitemapResponse","description":"Lightweight rows for the Next.js sitemap.ts builder."},"SnapshotOut":{"properties":{"snapshot_at":{"type":"string","title":"Snapshot At"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"avg_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Views"},"avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Engagement Rate"},"avg_likes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Likes"}},"type":"object","required":["snapshot_at","follower_count","avg_views","avg_engagement_rate","avg_likes"],"title":"SnapshotOut"},"SnapshotResponse":{"properties":{"username":{"type":"string","title":"Username"},"snapshot_created":{"type":"boolean","title":"Snapshot Created"}},"type":"object","required":["username","snapshot_created"],"title":"SnapshotResponse"},"StripeLifecycleEvent":{"properties":{"stripe_event_id":{"type":"string","maxLength":128,"minLength":4,"title":"Stripe Event Id"},"type":{"type":"string","maxLength":80,"minLength":4,"title":"Type"},"email":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Email"},"plan":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Plan"},"amount_cents":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Amount Cents"},"currency":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Currency"},"workspace_slug":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Workspace Slug"}},"type":"object","required":["stripe_event_id","type"],"title":"StripeLifecycleEvent","description":"Minimal payload the Vercel BFF forwards after signature verification.\n\nStripe's native event shape is huge; we accept just the fields the\nfanout actually uses. ``stripe_event_id`` is the ``evt_...`` id\nStripe sends — used for idempotency. ``type`` is the Stripe event\ntype (``checkout.session.completed`` etc.)."},"SummariseResponse":{"properties":{"summary":{"type":"string","title":"Summary"},"sentiment":{"type":"string","enum":["positive","neutral","negative","mixed"],"title":"Sentiment"},"key_quotes":{"items":{"type":"string"},"type":"array","title":"Key Quotes"},"next_recommended_action":{"type":"string","title":"Next Recommended Action"},"tokens_used":{"type":"integer","title":"Tokens Used"},"model":{"type":"string","title":"Model"}},"type":"object","required":["summary","sentiment","key_quotes","next_recommended_action","tokens_used","model"],"title":"SummariseResponse"},"TaskListOut":{"properties":{"tasks":{"items":{"$ref":"#/components/schemas/TaskOut"},"type":"array","title":"Tasks"}},"type":"object","required":["tasks"],"title":"TaskListOut"},"TaskOut":{"properties":{"id":{"type":"string","title":"Id"},"conversation_id":{"type":"string","title":"Conversation Id"},"title":{"type":"string","title":"Title"},"due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Due At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"assignee_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee User Id"},"recurring_rule_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recurring Rule Text"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"status":{"type":"string","enum":["pending","done","cancelled","failed"],"title":"Status","default":"pending"},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At"},"action_kind":{"type":"string","enum":["reminder","send_template","next_action_recompute"],"title":"Action Kind","default":"reminder"},"action_payload":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Action Payload"},"attempt_count":{"type":"integer","title":"Attempt Count","default":0},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["id","conversation_id","title","created_at"],"title":"TaskOut"},"TestConversationOut":{"properties":{"id":{"type":"string","title":"Id"},"kol_username":{"type":"string","title":"Kol Username"},"status":{"type":"string","title":"Status"},"last_event_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Event At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"event_count":{"type":"integer","title":"Event Count"}},"type":"object","required":["id","kol_username","status","last_event_at","created_at","event_count"],"title":"TestConversationOut"},"TestEventOut":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","title":"Kind"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"body_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Text"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"sender_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Email"},"sendgrid_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sendgrid Message Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","kind","direction","subject","body_text","body_html","sender_email","sendgrid_message_id","created_at"],"title":"TestEventOut"},"TrackVideoRequest":{"properties":{"url":{"type":"string","maxLength":1000,"minLength":5,"title":"Url","description":"TikTok video URL, Instagram reel/post URL, a vt.tiktok.com short link, or the whole share message — we extract the link."},"label":{"type":"string","title":"Label","description":"'own' (your collab) | 'competitor'","default":"own"},"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note"},"cadence_hours":{"anyOf":[{"type":"integer","maximum":168.0,"minimum":1.0},{"type":"null"}],"title":"Cadence Hours","description":"Refresh cadence; defaults to 24h"}},"type":"object","required":["url"],"title":"TrackVideoRequest"},"TrackedVideoOut":{"properties":{"id":{"type":"string","title":"Id"},"video_id":{"type":"string","title":"Video Id"},"video_url":{"type":"string","title":"Video Url"},"platform":{"type":"string","title":"Platform"},"label":{"type":"string","title":"Label"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"author_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Username"},"cadence_hours":{"type":"integer","title":"Cadence Hours"},"is_active":{"type":"boolean","title":"Is Active"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"next_refresh_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Refresh At"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","video_id","video_url","platform","label","note","author_username","cadence_hours","is_active","last_refreshed_at","next_refresh_at","last_error","created_at"],"title":"TrackedVideoOut"},"UpdateAutoJoinDomainsRequest":{"properties":{"domains":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Domains"}},"type":"object","title":"UpdateAutoJoinDomainsRequest"},"UpdateConversationRequest":{"properties":{"status":{"anyOf":[{"type":"string","enum":["lead","contacted","responded","negotiating","partner","declined","archived"]},{"type":"null"}],"title":"Status"},"assigned_to_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To User Id"}},"type":"object","title":"UpdateConversationRequest","description":"All fields optional — PATCH semantics."},"UpdateTaskRequest":{"properties":{"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At"},"recurrence_rule":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Recurrence Rule"},"action_kind":{"anyOf":[{"type":"string","enum":["reminder","send_template","next_action_recompute"]},{"type":"null"}],"title":"Action Kind"},"action_payload":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Action Payload"},"status":{"anyOf":[{"type":"string","enum":["done","cancelled"]},{"type":"null"}],"title":"Status"}},"type":"object","title":"UpdateTaskRequest","description":"PATCH a scheduled task. All fields optional — only fields\nexplicitly present in the JSON are touched.\n\n``action_payload`` is a sentinel field: send the key as ``null``\nto clear it, omit it to leave it alone."},"UpdateWorkspaceSettingsRequest":{"properties":{"brand_voice_text":{"anyOf":[{"type":"string","maxLength":20000},{"type":"null"}],"title":"Brand Voice Text"},"product_category":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Product Category"},"target_audience_text":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Target Audience Text"},"target_geographies":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Target Geographies"},"exclusions_text":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Exclusions Text"},"sender_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Sender Name"},"sender_title":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Sender Title"},"sender_brand_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Sender Brand Name"},"email_signature":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Email Signature"}},"type":"object","title":"UpdateWorkspaceSettingsRequest"},"UpsertCollectionRequest":{"properties":{"vertical":{"type":"string","maxLength":40,"title":"Vertical"},"public_slug":{"type":"string","maxLength":120,"minLength":2,"title":"Public Slug"},"title":{"type":"string","maxLength":200,"minLength":1,"title":"Title"},"subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subtitle"},"region":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Region"},"featured_rank":{"type":"integer","title":"Featured Rank","default":0},"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"}},"type":"object","required":["vertical","public_slug","title"],"title":"UpsertCollectionRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerticalOut":{"properties":{"vertical":{"type":"string","title":"Vertical"},"collections":{"items":{"$ref":"#/components/schemas/CollectionOut"},"type":"array","title":"Collections"}},"type":"object","required":["vertical","collections"],"title":"VerticalOut","description":"All featured collections for one vertical, members fully expanded — the\nsingle payload behind the /creators/{vertical} page."},"VideoBriefOut":{"properties":{"video_id":{"type":"string","title":"Video Id"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"likes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Likes"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"is_sponsored":{"type":"boolean","title":"Is Sponsored","default":false},"is_ad":{"type":"boolean","title":"Is Ad","default":false}},"type":"object","required":["video_id"],"title":"VideoBriefOut"},"VideoOut":{"properties":{"video_id":{"type":"string","title":"Video Id"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption"},"hashtags":{"items":{"type":"string"},"type":"array","title":"Hashtags"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"likes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Likes"},"comments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comments"},"shares":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Shares"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"},"music_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Music Title"},"is_ad":{"type":"boolean","title":"Is Ad"},"user_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Role"},"search_query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search Query"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"is_sponsored":{"type":"boolean","title":"Is Sponsored","default":false},"is_slideshow":{"type":"boolean","title":"Is Slideshow","default":false},"is_pinned":{"type":"boolean","title":"Is Pinned","default":false},"reposts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reposts"},"location_created":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Created"},"music_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Music Id"},"music_author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Music Author"},"music_original":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Music Original"},"mentions":{"items":{"type":"string"},"type":"array","title":"Mentions","default":[]}},"type":"object","required":["video_id","url","thumbnail_url","caption","hashtags","views","likes","comments","shares","duration_seconds","music_title","is_ad","user_role","search_query","created_at"],"title":"VideoOut"},"WatchlistAdd":{"properties":{"username":{"type":"string","maxLength":100,"minLength":1,"title":"Username"},"platform":{"type":"string","maxLength":20,"minLength":1,"title":"Platform","default":"tiktok"}},"type":"object","required":["username"],"title":"WatchlistAdd"},"WatchlistAudiencePrefs":{"properties":{"audience_auto_refresh":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Audience Auto Refresh"},"audience_refresh_interval_days":{"anyOf":[{"type":"integer","maximum":90.0,"minimum":1.0},{"type":"null"}],"title":"Audience Refresh Interval Days"},"audience_sample_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audience Sample Size"}},"type":"object","title":"WatchlistAudiencePrefs","description":"PATCH-shape for ``PUT /api/watchlist/{username}/audience``.\n\nAll fields optional so the front-end toggle can flip just\n``audience_auto_refresh`` without re-sending the others."},"WatchlistDigestResponse":{"properties":{"digest":{"$ref":"#/components/schemas/WatchlistDigestSummary"},"entries":{"items":{"$ref":"#/components/schemas/WatchlistEntryDigest"},"type":"array","title":"Entries"}},"type":"object","required":["digest","entries"],"title":"WatchlistDigestResponse"},"WatchlistDigestSummary":{"properties":{"total":{"type":"integer","title":"Total"},"n_act_now":{"type":"integer","title":"N Act Now"},"n_notable":{"type":"integer","title":"N Notable"},"n_quiet":{"type":"integer","title":"N Quiet"},"n_new_viral_videos":{"type":"integer","title":"N New Viral Videos"},"n_newly_dormant":{"type":"integer","title":"N Newly Dormant"},"total_unseen":{"type":"integer","title":"Total Unseen"}},"type":"object","required":["total","n_act_now","n_notable","n_quiet","n_new_viral_videos","n_newly_dormant","total_unseen"],"title":"WatchlistDigestSummary","description":"Top-level roll-up — answers \"what changed across my watchlist?\"\nin seven integers so the UI can render a header without scanning\nevery entry."},"WatchlistEntryDigest":{"properties":{"id":{"type":"string","title":"Id"},"added_at":{"type":"string","title":"Added At"},"is_paused":{"type":"boolean","title":"Is Paused"},"platform":{"type":"string","title":"Platform","default":"tiktok"},"audience_auto_refresh":{"type":"boolean","title":"Audience Auto Refresh","default":false},"audience_refresh_interval_days":{"type":"integer","title":"Audience Refresh Interval Days","default":7},"audience_sample_size":{"type":"integer","title":"Audience Sample Size","default":200},"audience_last_refreshed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience Last Refreshed At"},"identity":{"$ref":"#/components/schemas/IdentityOut"},"current":{"$ref":"#/components/schemas/CurrentStatsOut"},"changes":{"$ref":"#/components/schemas/ChangesOut"},"signals":{"items":{"$ref":"#/components/schemas/SignalOut"},"type":"array","title":"Signals","default":[]},"latest_video":{"anyOf":[{"$ref":"#/components/schemas/VideoBriefOut"},{"type":"null"}]},"days_since_last_post":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Since Last Post"},"outreach":{"anyOf":[{"$ref":"#/components/schemas/OutreachBriefOut"},{"type":"null"}]},"recommendation_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recommendation Score"},"significance":{"$ref":"#/components/schemas/SignificanceOut"},"unseen":{"type":"boolean","title":"Unseen","default":false}},"type":"object","required":["id","added_at","is_paused","identity","current","changes","significance"],"title":"WatchlistEntryDigest","description":"One watched KOL, fully change-oriented — no per-row follow-up call."},"WatchlistEntryOut":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"username":{"type":"string","title":"Username"},"platform":{"type":"string","title":"Platform","default":"tiktok"},"added_at":{"type":"string","title":"Added At"},"last_refreshed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Refreshed At"},"next_refresh_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Refresh At"},"is_paused":{"type":"boolean","title":"Is Paused"},"audience_auto_refresh":{"type":"boolean","title":"Audience Auto Refresh","default":false},"audience_refresh_interval_days":{"type":"integer","title":"Audience Refresh Interval Days","default":7},"audience_sample_size":{"type":"integer","title":"Audience Sample Size","default":200},"audience_last_refreshed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience Last Refreshed At"}},"type":"object","required":["id","user_id","username","added_at","is_paused"],"title":"WatchlistEntryOut"},"WorkspaceDetailOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"owner_user_id":{"type":"string","title":"Owner User Id"},"plan":{"type":"string","title":"Plan"},"role":{"anyOf":[{"type":"string","enum":["admin","billing","member"]},{"type":"null"}],"title":"Role"},"member_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Member Count"},"is_personal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Personal"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"members":{"items":{"$ref":"#/components/schemas/MemberOut"},"type":"array","title":"Members"},"invitations":{"anyOf":[{"items":{"$ref":"#/components/schemas/InvitationOut"},"type":"array"},{"type":"null"}],"title":"Invitations"}},"type":"object","required":["id","name","slug","owner_user_id","plan","created_at","updated_at","members"],"title":"WorkspaceDetailOut"},"WorkspaceListOut":{"properties":{"workspaces":{"items":{"$ref":"#/components/schemas/WorkspaceOut"},"type":"array","title":"Workspaces"}},"type":"object","required":["workspaces"],"title":"WorkspaceListOut"},"WorkspaceOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"owner_user_id":{"type":"string","title":"Owner User Id"},"plan":{"type":"string","title":"Plan"},"role":{"anyOf":[{"type":"string","enum":["admin","billing","member"]},{"type":"null"}],"title":"Role"},"member_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Member Count"},"is_personal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Personal"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","slug","owner_user_id","plan","created_at","updated_at"],"title":"WorkspaceOut"},"WorkspaceSettingsOut":{"properties":{"id":{"type":"string","title":"Id"},"slug":{"type":"string","title":"Slug"},"brand_voice_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Voice Text"},"product_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Category"},"target_audience_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audience Text"},"target_geographies":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Geographies"},"exclusions_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exclusions Text"},"sender_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Name"},"sender_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Title"},"sender_brand_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Brand Name"},"email_signature":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Signature"},"ai_credits_used_today":{"type":"integer","title":"Ai Credits Used Today","default":0},"ai_credits_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Credits Day"},"ai_daily_token_cap":{"type":"integer","title":"Ai Daily Token Cap","default":0}},"type":"object","required":["id","slug"],"title":"WorkspaceSettingsOut","description":"Admin-visible settings on a workspace. Phase 2 ships\n``brand_voice_text`` (the AI compose doc) and the daily-credits\ncounter so the settings UI can show usage / cap. Phase 3 (EPIC\n#198) adds the four structured brand-profile fields used by the\nKOL brand-fit scoring path."},"XScrapeRequest":{"properties":{"query":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Query"},"handles":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Handles"},"max_tweets":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Max Tweets","default":50}},"type":"object","title":"XScrapeRequest","description":"Either ``query`` or ``handles`` must be supplied — not both.\n``max_tweets`` follows the IG / YT semantics: the number of recent\ntweets to pull per handle (NOT the total across the batch)."},"XScrapeResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"keyword":{"type":"string","title":"Keyword"},"estimated_seconds":{"type":"integer","title":"Estimated Seconds"},"query_plan":{"anyOf":[{"$ref":"#/components/schemas/QueryPlanEcho"},{"type":"null"}]}},"type":"object","required":["job_id","status","keyword","estimated_seconds"],"title":"XScrapeResponse"},"_KolListMembership":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"_KolListMembership"},"api__routes__admin_collections__AddMembersRequest":{"properties":{"usernames":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Usernames"}},"type":"object","required":["usernames"],"title":"AddMembersRequest"},"api__schemas__lists__AddMembersRequest":{"properties":{"usernames":{"items":{"type":"string"},"type":"array","title":"Usernames"}},"type":"object","required":["usernames"],"title":"AddMembersRequest"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}