{"openapi":"3.1.0","info":{"title":"LiveReplays API","description":"The API origin powering livereplays.cc — TikTok live monitoring, automatic stream recording, replays and clips.","version":"0.1.0"},"paths":{"/api/v1/users":{"post":{"tags":["users"],"summary":"Add User","description":"Add a TikTok user to the monitoring list.\n\nThe system will automatically check if the user goes live and start recording.\nIf the user was previously deactivated, they will be reactivated.\nEnforces per-tier limit on monitored profiles.","operationId":"add_user_api_v1_users_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Bad Request"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["users"],"summary":"Get Users","description":"Get all users (active and inactive) with their current states.\n\nThe is_active field indicates whether the user is being monitored.","operationId":"get_users_api_v1_users_get","parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponse"},"title":"Response Get Users Api V1 Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/with-recordings":{"get":{"tags":["users"],"summary":"Get Users With Recordings","description":"Get users who have at least one recording, along with their recording count.\n\nQuery params:\n    scope=all  — return ALL creators with recordings (admin/PIN only)\n    scope=mine — return only creators the caller monitors (junction table)\n    (default)  — 'all' for admin, 'mine' for regular users","operationId":"get_users_with_recordings_api_v1_users_with_recordings_get","parameters":[{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserWithRecordingsResponse"},"title":"Response Get Users With Recordings Api V1 Users With Recordings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{username}/avatar":{"get":{"tags":["users"],"summary":"Get User Avatar","description":"Serve user's locally saved profile photo.\n\nReturns the local profile.jpg file if it exists,\notherwise returns a 404.\n\nAvailable to any authenticated user — avatars are public profile data\nthat the frontend needs to render every monitored creator card.","operationId":"get_user_avatar_api_v1_users__username__avatar_get","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9._]{2,30}$","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"image/jpeg":{}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}/deactivate":{"patch":{"tags":["users"],"summary":"Deactivate User","description":"Soft-deactivate a user.\n\nThe user is excluded from monitoring but their data and recordings are preserved.","operationId":"deactivate_user_api_v1_users__user_id__deactivate_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}/activate":{"patch":{"tags":["users"],"summary":"Activate User","description":"Reactivate a previously deactivated user.\n\nThe user will be included in monitoring again.\nOpted-out creators (#302) cannot be reactivated — the ban is durable.","operationId":"activate_user_api_v1_users__user_id__activate_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{tiktok_user_id}/unsubscribe":{"delete":{"tags":["users"],"summary":"Unsubscribe From Creator","description":"Remove a creator from the caller's monitoring list.\n\nFree users must wait `monitor_cooldown_days` (default 7) after adding\na creator before they can remove them.  Pro/Admin can remove immediately.\n\nThis does NOT delete the TikTok user or their recordings.  If no other\nuser monitors the same creator, the system will auto-deactivate them\nafter the orphan grace period (default 14 days).","operationId":"unsubscribe_from_creator_api_v1_users__tiktok_user_id__unsubscribe_delete","parameters":[{"name":"tiktok_user_id","in":"path","required":true,"schema":{"type":"string","title":"Tiktok User Id"}}],"responses":{"204":{"description":"Successful Response"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/monitoring-stats":{"get":{"tags":["users"],"summary":"Get Monitoring Stats","description":"Get TikTok user counts by monitoring status (super_admin only).","operationId":"get_monitoring_stats_api_v1_users_monitoring_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Monitoring Stats Api V1 Users Monitoring Stats Get"}}}}}}},"/api/v1/users/bulk-standby":{"post":{"tags":["users"],"summary":"Bulk Standby","description":"Set ALL non-LIVE/non-RECORDING TikTok users to STANDBY.\n\nThis is the \"danger button\" — it:\n1. Runs inline migration (adds monitoring_status column if missing)\n2. Sets all eligible users to STANDBY + is_active=False\n3. Skips users currently LIVE or RECORDING (don't interrupt streams)\n\nReturns count of affected and skipped users.","operationId":"bulk_standby_api_v1_users_bulk_standby_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Bulk Standby Api V1 Users Bulk Standby Post"}}}}}}},"/api/v1/users/search":{"get":{"tags":["users"],"summary":"Search Creators","description":"Search all platform creators by username (case-insensitive substring match).\n\nReturns lightweight results for the search dropdown.\nExcludes INVALIDATED (soft-deleted) users. Capped at `limit` results.","operationId":"search_creators_api_v1_users_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{},"title":"Response Search Creators Api V1 Users Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}":{"get":{"tags":["users"],"summary":"Get User","description":"Get a specific user by ID.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["users"],"summary":"Remove User","description":"Remove a user from the monitoring list.\n\nThis will also delete all associated recordings and soft-delete the user to INVALIDATED state.","operationId":"remove_user_api_v1_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"204":{"description":"Successful Response"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}/hard":{"delete":{"tags":["users"],"summary":"Hard Delete User","description":"PERMANENTLY remove a TikTok creator and ALL associated data.\n\nThis is irreversible. Unlike `DELETE /users/{user_id}` (soft-delete to\nINVALIDATED), this:\n\n- Stops any active FFmpeg recording\n- Deletes every recording (file, thumbnails, B2 cloud copy, DB row)\n- Removes the cached avatar and the entire `recordings/<username>/` folder\n- DELETEs the user row itself (CASCADE removes user_monitors and\n  user_favorites via the recordings FK)\n\nReturns the username, count of recordings deleted, and bytes freed.","operationId":"hard_delete_user_api_v1_users__user_id__hard_delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Hard Delete User Api V1 Users  User Id  Hard Delete"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}/state":{"patch":{"tags":["users"],"summary":"Update User State","description":"Manually override a TikTok creator's current_state (super_admin only).\n\nOnly admin-safe states are allowed: IDLE, ERROR, INVALIDATED.\nSystem-driven states (LIVE, RECORDING, COMPLETED) cannot be set manually\nto prevent desyncing from the actual TikTok live status.","operationId":"update_user_state_api_v1_users__user_id__state_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"IDLE","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update User State Api V1 Users  User Id  State Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/{user_id}/monitoring-status":{"patch":{"tags":["users"],"summary":"Update Monitoring Status","description":"Toggle a single user's monitoring status (super_admin only).\n\nValid values: ACTIVE, STANDBY, DEACTIVATED.\nSetting to ACTIVE also sets is_active=True and triggers an immediate check.\nSetting to STANDBY/DEACTIVATED sets is_active=False.","operationId":"update_monitoring_status_api_v1_users__user_id__monitoring_status_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"monitoring_status","in":"query","required":false,"schema":{"type":"string","default":"ACTIVE","title":"Monitoring Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Monitoring Status Api V1 Users  User Id  Monitoring Status Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings":{"get":{"tags":["recordings"],"summary":"Get Recordings","description":"Get recordings with full details, optionally filtered by TikTok user.\n\n- **Super admins / moderators**: see all recordings (no restrictions).\n  Mods need the full corpus to do review work; they don't monitor any\n  creators so a junction-table scope would always return [].\n- **Regular users**: scoped to their monitored TikTok users only.\n  If `user_id` is provided, the user must monitor that TikTok user.\n  If `user_id` is omitted, returns recordings for all monitored users.","operationId":"get_recordings_api_v1_recordings_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TikTok user ID","title":"User Id"},"description":"Filter by TikTok user ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecordingDetailResponse"},"title":"Response Get Recordings Api V1 Recordings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/top-peaks":{"get":{"tags":["recordings"],"summary":"Get Top Peaks","description":"Get top recordings ranked by peak viewer count within a time period.","operationId":"get_top_peaks_api_v1_recordings_top_peaks_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Time period: today, yesterday, 3d, 7d, 30d, 6m, 1y, all","default":"7d","title":"Period"},"description":"Time period: today, yesterday, 3d, 7d, 30d, 6m, 1y, all"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Max results","default":10,"title":"Limit"},"description":"Max results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecordingDetailResponse"},"title":"Response Get Top Peaks Api V1 Recordings Top Peaks Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/visibility":{"patch":{"tags":["recordings"],"summary":"Update visibility of a single recording","description":"Set a recording's visibility to PUBLIC, UNLISTED, or PRIVATE.","operationId":"update_recording_visibility_api_v1_recordings__recording_id__visibility_patch","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisibilityUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Recording Visibility Api V1 Recordings  Recording Id  Visibility Patch"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/bulk-visibility":{"post":{"tags":["recordings"],"summary":"Update visibility for multiple recordings at once","description":"Batch-set visibility for the given recording IDs.","operationId":"bulk_update_recording_visibility_api_v1_recordings_bulk_visibility_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVisibilityUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Bulk Update Recording Visibility Api V1 Recordings Bulk Visibility Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/triage":{"get":{"tags":["recordings"],"summary":"Get recordings for triage with auto-classification hints","description":"Return recordings with auto-classification hints for fast curation.","operationId":"get_triage_recordings_api_v1_recordings_triage_get","parameters":[{"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":"hint","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by hint: garbage, likely_good, review","title":"Hint"},"description":"Filter by hint: garbage, likely_good, review"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Triage Recordings Api V1 Recordings Triage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/purge-preview":{"get":{"tags":["recordings"],"summary":"Dry-run auto-purge: shows what would be deleted right now","description":"Preview what the auto-purge worker would do if it ran right now.\n\nScores all completed recordings and returns those that fall below\nthe configured (or custom) threshold, grouped by user.","operationId":"get_purge_preview_api_v1_recordings_purge_preview_get","parameters":[{"name":"custom_threshold","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Override the default threshold. If provided, this is used instead of the config value.","title":"Custom Threshold"},"description":"Override the default threshold. If provided, this is used instead of the config value."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurgePreviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/run-purge":{"post":{"tags":["recordings"],"summary":"Execute auto-purge now — immediately deletes recordings below threshold","description":"Run the auto-purge cycle immediately on demand.\n\nOptionally accepts { \"user_ids\": [\"id1\", \"id2\"] } to only purge\nrecordings belonging to specific users. If omitted, purges all\nrecordings below threshold.","operationId":"run_purge_now_api_v1_recordings_run_purge_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/storage-by-user":{"get":{"tags":["recordings"],"summary":"Per-user aggregated storage usage with retention scores","description":"Aggregated storage usage grouped by TikTok user.\n\nEach entry contains total file size, recording count, average viewers,\nand average retention score across all their completed recordings.","operationId":"get_storage_by_user_api_v1_recordings_storage_by_user_get","parameters":[{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort field: total_size, recording_count, avg_viewers, avg_retention_score, last_recording_at","default":"total_size","title":"Sort By"},"description":"Sort field: total_size, recording_count, avg_viewers, avg_retention_score, last_recording_at"},{"name":"sort_asc","in":"query","required":false,"schema":{"type":"boolean","description":"Sort ascending","default":false,"title":"Sort Asc"},"description":"Sort ascending"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by username","title":"Search"},"description":"Filter by username"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserStorageResponse"},"title":"Response Get Storage By User Api V1 Recordings Storage By User Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/storage-overview":{"get":{"tags":["recordings"],"summary":"Paginated recordings sorted by size with retention scores","description":"Paginated storage overview. Returns recordings sorted by chosen field,\neach annotated with its retention_score.\nTotal count is returned in X-Total-Count header.","operationId":"get_storage_overview_api_v1_recordings_storage_overview_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of items to skip","default":0,"title":"Offset"},"description":"Number of items to skip"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Max items to return","default":50,"title":"Limit"},"description":"Max items to return"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort field: file_size, duration, started_at, viewer_count","default":"file_size","title":"Sort By"},"description":"Sort field: file_size, duration, started_at, viewer_count"},{"name":"sort_asc","in":"query","required":false,"schema":{"type":"boolean","description":"Sort ascending","default":false,"title":"Sort Asc"},"description":"Sort ascending"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by username","title":"Search"},"description":"Filter by username"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecordingDetailResponse"},"title":"Response Get Storage Overview Api V1 Recordings Storage Overview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/gallery":{"get":{"tags":["recordings"],"summary":"Paginated recordings for the public library gallery","description":"Paginated gallery of completed recordings for the library.\nAccessible to any authenticated user (not admin-only).\nReturns recordings sorted by ended_at desc by default.\nTotal count is returned in X-Total-Count header.\n\n``scope='all'`` is the global catalogue and requires a paid or staff role\n(#317). The ``Literal`` annotation is the other half of that gate: an\nunrecognised value used to fall past the ``== \"mine\"`` branch straight into\nthe god-view, so FastAPI now rejects it with a 422 before the handler runs.","operationId":"get_gallery_api_v1_recordings_gallery_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of items to skip","default":0,"title":"Offset"},"description":"Number of items to skip"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max items per page","default":24,"title":"Limit"},"description":"Max items per page"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort field: ended_at, started_at, duration, viewer_count, file_size","default":"ended_at","title":"Sort By"},"description":"Sort field: ended_at, started_at, duration, viewer_count, file_size"},{"name":"sort_asc","in":"query","required":false,"schema":{"type":"boolean","description":"Sort ascending","default":false,"title":"Sort Asc"},"description":"Sort ascending"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by username","title":"Search"},"description":"Filter by username"},{"name":"scope","in":"query","required":false,"schema":{"enum":["all","mine"],"type":"string","description":"'all' for global, 'mine' for user's monitored list","default":"all","title":"Scope"},"description":"'all' for global, 'mine' for user's monitored list"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecordingDetailResponse"},"title":"Response Get Gallery Api V1 Recordings Gallery Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/vault/usage":{"get":{"tags":["recordings"],"summary":"Get your vault storage usage and quota","description":"Returns the user's current vault usage vs their plan quota.","operationId":"get_vault_usage_api_v1_recordings_vault_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/recordings/favorites":{"get":{"tags":["recordings"],"summary":"Get current user's favorited recordings","description":"Return all recordings favorited by the current user, newest first.","operationId":"get_favorites_api_v1_recordings_favorites_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/recordings/{recording_id}":{"get":{"tags":["recordings"],"summary":"Get Recording","description":"Get detailed information about a specific recording.\n\nPermission rules:\n  - super_admin: full access (including PRIVATE / DMCA_REMOVED)\n  - PUBLIC / UNLISTED recordings: any authenticated user\n  - Recordings whose creator the user monitors: allowed (treated as \"own\")\n  - DMCA_REMOVED to non-admins: 404 (hide existence)\n  - Otherwise: 403","operationId":"get_recording_api_v1_recordings__recording_id__get","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingDetailResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["recordings"],"summary":"Delete a recording (super_admin: unilateral; moderator: requires 2-of-N approval)","description":"Permanently delete a recording.\n\nBehaviour by role:\n  * ``super_admin`` — can delete unilaterally (operational escape hatch\n    for legal/emergency takedowns). Every super_admin delete is still\n    logged in ``moderation_actions`` as ``DELETE_EXECUTED`` for audit.\n  * ``moderator`` — delegated to the four-eyes vote ledger. Direct DELETE\n    from a mod returns 403 ``requires_two_mod_approval`` to push them to\n    ``POST /moderation/vote-delete/{id}`` instead. This keeps a single\n    path for all multi-sig deletions.","operationId":"delete_recording_api_v1_recordings__recording_id__delete","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/viewer-timeline":{"get":{"tags":["recordings"],"summary":"Get Viewer Timeline","description":"Get the viewer count time-series for a recording.","operationId":"get_viewer_timeline_api_v1_recordings__recording_id__viewer_timeline_get","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerTimelineResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/file":{"get":{"tags":["recordings"],"summary":"Download Recording","description":"Download the recording file (triggers browser download).\nRequires Starter (own recordings) or Pro (any recording).","operationId":"download_recording_api_v1_recordings__recording_id__file_get","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/stream":{"get":{"tags":["recordings"],"summary":"Stream Recording","description":"Stream the recording file for inline playback with proper HTTP Range support.\n\nHandles Range requests (206 Partial Content) so the browser can seek\nwithout re-downloading the entire file. This is critical for large recordings.\nRequires Starter+ plan — free users can stream own recordings (monitored users).","operationId":"stream_recording_api_v1_recordings__recording_id__stream_get","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"416":{"description":"Range not satisfiable"},"206":{"description":"Partial Content"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/thumbnails/{index}":{"get":{"tags":["recordings"],"summary":"Get Recording Thumbnail","description":"Get a specific thumbnail for a recording.\n\nIndex must be 1-4, representing thumbnails at 25%, 50%, 75%, 100% of video.","operationId":"get_recording_thumbnail_api_v1_recordings__recording_id__thumbnails__index__get","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}},{"name":"index","in":"path","required":true,"schema":{"type":"integer","title":"Index"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/star":{"patch":{"tags":["recordings"],"summary":"Toggle star on a recording (admin only)","description":"Toggle is_starred flag. Starred recordings are never auto-purged\nby the storage worker.","operationId":"toggle_star_api_v1_recordings__recording_id__star_patch","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/favorite":{"post":{"tags":["recordings"],"summary":"Toggle favorite on a recording (any authenticated user)","description":"Toggle favorite for the current user. Returns the new state.","operationId":"toggle_favorite_api_v1_recordings__recording_id__favorite_post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recordings/{recording_id}/save":{"post":{"tags":["recordings"],"summary":"Save a recording to your permanent vault (exempt from auto-delete)","description":"Copy a recording to the user's permanent vault storage.\n\nA copy is placed under the vault/ prefix in B2 (no lifecycle rule — it\npersists until the user removes it) and the recording is flagged\n``is_saved``, which exempts it from the storage worker's retention-score\npurge, including emergency low-disk escalation (#303). DMCA takedowns\nstill override the vault: the 30-day DMCA sweep hard-deletes the row\nregardless of ``is_saved``. The vault is a Pro-only feature; free users\nreceive 403.","operationId":"save_to_vault_api_v1_recordings__recording_id__save_post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["recordings"],"summary":"Remove a recording from your vault (it will be auto-deleted later)","description":"Remove a recording from the user's permanent vault.\n\nThe recording stays in the main storage but will be subject to\nlifecycle auto-delete rules again.","operationId":"remove_from_vault_api_v1_recordings__recording_id__save_delete","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/health":{"get":{"tags":["health"],"summary":"Health Check","description":"Internal health check — detailed system status including worker internals.\n\nsuper_admin only. It discloses the worker map (names, liveness, restart\ncounts, uptime), fleet size and live-stream activity — and\n``active_recordings`` is a field its sibling ``GET /system/stats`` already\ngates behind the same role, while the public ``/status`` redacts it on\npurpose. Anyone wanting a liveness signal should use ``/status`` or\n``/ping``.","operationId":"health_check_api_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Check Api V1 Health Get"}}}}}}},"/api/v1/status":{"get":{"tags":["health"],"summary":"Public Status","description":"Public status endpoint — sanitized system health for the status page.\n\nReturns only safe-to-expose information:\n- Overall status (operational / degraded / down)\n- Service component health (API, database, workers)\n- Uptime duration\n- No internal details (worker names, user counts, etc.)\n\nThat last line is a commitment, not a description, and it was false for\n``recording_engine.active_sessions`` until #348. Nothing here may carry a\ncount of live or recording creators, in any component, at any granularity:\nthis route is unauthenticated, rate-limited only per-IP, and rendered on an\nindexable page that polls it every 30s, so any count published here becomes\na retroactive time series in search caches. Counts belong on ``/health``.","operationId":"public_status_api_v1_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Public Status Api V1 Status Get"}}}}}}},"/api/v1/system/stats":{"get":{"tags":["system"],"summary":"Get System Stats","description":"Get current system statistics.\n\nReturns real-time metrics for:\n- Storage usage (recordings folder)\n- Memory (RAM) usage\n- Network I/O rates\n- Active and total recordings\n- Server uptime\n- CPU usage\n- Feature toggle states","operationId":"get_system_stats_api_v1_system_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemStats"}}}}}}},"/api/v1/system/proxy-health":{"get":{"tags":["system"],"summary":"Get Proxy Health","description":"Get real-time health status for all configured SOCKS5 proxies.\n\nShows per-proxy stats including consecutive failures, last error types,\nand cycle-level success/error counts.","operationId":"get_proxy_health_api_v1_system_proxy_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxyHealthResponse"}}}}}}},"/api/v1/system/proxy-health/test":{"post":{"tags":["system"],"summary":"Test Proxy Reachability","description":"Run a diagnostic reachability test on all proxies.\n\nTests each proxy against Google (service health) and TikTok (IP block check).\nThis distinguishes between \"proxy service is down\" vs \"TikTok is blocking this IP\".","operationId":"test_proxy_reachability_api_v1_system_proxy_health_test_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxyReachabilityResponse"}}}}}}},"/api/v1/system/monitoring/toggle":{"post":{"tags":["system"],"summary":"Toggle Monitoring","description":"Toggle the monitoring worker on or off.\n\nWhen disabled, the monitoring worker will skip checking users for live activity.\nExisting recordings will continue.","operationId":"toggle_monitoring_api_v1_system_monitoring_toggle_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleResponse"}}}},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/recording/toggle":{"post":{"tags":["system"],"summary":"Toggle Recording","description":"Toggle the recording worker on or off.\n\nWhen disabled, new recordings will not start, but existing active recordings\nwill continue to completion.","operationId":"toggle_recording_api_v1_system_recording_toggle_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleResponse"}}}},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/settings":{"get":{"tags":["system"],"summary":"Get Admin Settings","description":"Get current runtime admin settings (super_admin only).","operationId":"get_admin_settings_api_v1_system_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingsResponse"}}}}}},"post":{"tags":["system"],"summary":"Update Admin Settings","description":"Update runtime admin settings (super_admin only).\n\nPersists to DB so value survives backend restarts.","operationId":"update_admin_settings_api_v1_system_settings_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingsUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/storage-worker/toggle":{"post":{"tags":["system"],"summary":"Toggle Storage Worker","description":"Toggle the storage auto-purge worker on or off.\n\nWhen disabled, the storage worker will skip its purge cycles.\nNo recordings will be automatically deleted.","operationId":"toggle_storage_worker_api_v1_system_storage_worker_toggle_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleResponse"}}}},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/sessions":{"get":{"tags":["system"],"summary":"List Sessions","description":"Get all TikTok sessions in the pool with usage status.\n\nReturns masked sessionid values, usage counts, and last-used times.\nUse this to monitor session rotation health.","operationId":"list_sessions_api_v1_system_sessions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionPoolResponse"}}}}}},"post":{"tags":["system"],"summary":"Add Session","description":"Add a TikTok session to the pool.\n\nEach session is a pair of (sessionid, msToken) from a TikTok account.\nMultiple sessions distribute API load and reduce detection risk.\n\nTo obtain these values:\n1. Open TikTok in a browser and log in\n2. Open DevTools (F12) → Application/Storage → Cookies\n3. Copy 'sessionid' and 'msToken'","operationId":"add_session_api_v1_system_sessions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSessionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/sessions/{session_id}":{"delete":{"tags":["system"],"summary":"Remove Session","description":"Remove a TikTok session from the pool.\n\nThe session will no longer be used for API calls.\nActive recordings using this session will continue until they finish.","operationId":"remove_session_api_v1_system_sessions__session_id__delete","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["system"],"summary":"Update Session","description":"Toggle a session's enabled state and/or update its label.\n\nDisabling removes it from live rotation; re-enabling reloads it. Both\nfields are optional — send only what you want to change.","operationId":"update_session_api_v1_system_sessions__session_id__patch","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionPoolEntry"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/sessions/{session_id}/evict":{"post":{"tags":["system"],"summary":"Evict Session","description":"Soft-quarantine a session: skip it from rotation for a cooldown window.\n\nReuses the ban-meter eviction mechanism (#242). The session stays in the\npool but is excluded from selection until the cooldown lapses, letting the\noperator sideline a suspected-throttled cookie without deleting it.","operationId":"evict_session_api_v1_system_sessions__session_id__evict_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionPoolEntry"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/sessions/{session_id}/reset-usage":{"post":{"tags":["system"],"summary":"Reset Session Usage","description":"Clear an orphaned active_usage_count to 0.\n\nUse when a crashed recording left a session's in-use counter stuck above\nthe real number of holders, starving it in the least-loaded rotation.","operationId":"reset_session_usage_api_v1_system_sessions__session_id__reset_usage_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionPoolEntry"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/cookies":{"get":{"tags":["system"],"summary":"Get Cookies","description":"Deprecated — use GET /sessions instead.","operationId":"get_cookies_api_v1_system_cookies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CookiesResponse"}}}}},"deprecated":true}},"/api/v1/system/sync":{"post":{"tags":["system"],"summary":"Sync Storage","description":"Synchronize filesystem storage with database records.\n\nThis endpoint scans the storage directory and reconciles with the database:\n- Creates DB entries for files that exist on disk but not in database\n- Marks DB entries as FAILED/MISSING if the file no longer exists on disk\n\nThe filesystem is the source of truth.\n\nUse this after:\n- Migrating recordings to a new storage location\n- Restoring from backup\n- Moving files manually","operationId":"sync_storage_api_v1_system_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncResponse"}}}}}}},"/api/v1/system/alerts":{"get":{"tags":["system"],"summary":"Get System Alerts","description":"Get the most recent system operational alerts (up to 100).\nOrdered newest first.","operationId":"get_system_alerts_api_v1_system_alerts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SystemAlert"},"title":"Response Get System Alerts Api V1 System Alerts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/alerts/test":{"post":{"tags":["system"],"summary":"Trigger Test Alert","description":"TEMPORARY endpoint to trigger alerts for UI testing.","operationId":"trigger_test_alert_api_v1_system_alerts_test_post","parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","default":"critical","title":"Category"}},{"name":"message","in":"query","required":false,"schema":{"type":"string","default":"Test alert from verification phase","title":"Message"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/proxies":{"get":{"tags":["system"],"summary":"List Proxies","description":"Get the current list of proxies in the rotation.","operationId":"list_proxies_api_v1_system_proxies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["system"],"summary":"Add Proxy","description":"Add a SOCKS5 proxy to the rotation.\n\nTests connectivity before adding. Persists to database.","operationId":"add_proxy_api_v1_system_proxies_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddProxyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"},"502":{"description":"Bad gateway"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["system"],"summary":"Remove Proxy","description":"Remove a proxy from the rotation.\n\nCannot remove the last proxy. Deletes from database.","operationId":"remove_proxy_api_v1_system_proxies_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveProxyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/proxies/provision":{"post":{"tags":["system"],"summary":"Provision Vultr Proxy","description":"Provision a new SOCKS5 proxy via Vultr API.\n\nCreates a VPS, auto-configures microsocks via cloud-init,\nwaits for it to come online (~90s), and adds to the rotation.\n\nRequires VULTR_API_KEY env var.","operationId":"provision_vultr_proxy_api_v1_system_proxies_provision_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionVultrRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"},"504":{"description":"Gateway timeout"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/proxies/vultr":{"delete":{"tags":["system"],"summary":"Destroy Vultr Proxy","description":"Destroy a Vultr proxy instance and remove from rotation.","operationId":"destroy_vultr_proxy_api_v1_system_proxies_vultr_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DestroyVultrRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/proxies/vultr/regions":{"get":{"tags":["system"],"summary":"Get Vultr Regions","description":"Get available Vultr regions for the provision dialog.","operationId":"get_vultr_regions_api_v1_system_proxies_vultr_regions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}}}},"/api/v1/system/proxies/ssh-setup":{"post":{"tags":["system"],"summary":"Setup Proxy Via Ssh","description":"Set up a SOCKS5 proxy on any VPS via SSH.\n\nSSHs into the server, installs microsocks, configures firewall,\nwaits for SOCKS5 to come online, and adds to the proxy rotation.\n\nWorks with any provider — just needs root SSH access.\nTakes ~60-90 seconds.","operationId":"setup_proxy_via_ssh_api_v1_system_proxies_ssh_setup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSHSetupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"},"502":{"description":"Bad gateway"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/proxy-recommendation":{"get":{"tags":["system"],"summary":"Get Proxy Recommendation","description":"Calculate how many proxies are needed for the current user count.\n\nFormula: ceil(users / 150), minimum 1 if any users exist.","operationId":"get_proxy_recommendation_api_v1_system_proxy_recommendation_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/proxy-override":{"post":{"tags":["system"],"summary":"Set Proxy Override","description":"Override the proxy recommendation guard.\n\nAllows monitoring to proceed even with fewer proxies than recommended.\nThe override persists until the user adds sufficient proxies (auto-clears).","operationId":"set_proxy_override_api_v1_system_proxy_override_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/metrics":{"get":{"tags":["system"],"summary":"Get Performance Metrics","description":"Return a JSON snapshot of all in-process performance metrics.\n\nIncludes:\n- Timers: latency histograms with P50/P95/P99/avg/min/max for every\n  instrumented operation (monitoring cycles, API calls, recording lifecycle, etc.)\n- Counters: event counts with rate-per-minute (cache hits, errors, throttles, etc.)\n- Uptime: seconds since the metrics collector was initialized","operationId":"get_performance_metrics_api_v1_system_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/scaling/vps":{"get":{"tags":["system"],"summary":"Get Vps Scaling Status","description":"Get current VPS info and scaling recommendation.\n\nReturns server type, RAM usage, and whether a resize is recommended.","operationId":"get_vps_scaling_status_api_v1_system_scaling_vps_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/scaling/vps/resize":{"post":{"tags":["system"],"summary":"Resize Vps","description":"Resize the main VPS. ⚠️ Causes 30–90s downtime!\n\nActive recordings will be interrupted. Requires confirm=true.","operationId":"resize_vps_api_v1_system_scaling_vps_resize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VpsResizeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/storage/b2":{"get":{"tags":["system"],"summary":"Get B2 Storage Status","description":"Get B2 bucket usage, cost estimate, and lifecycle rules.","operationId":"get_b2_storage_status_api_v1_system_storage_b2_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/storage/b2/lifecycle":{"post":{"tags":["system"],"summary":"Configure B2 Lifecycle","description":"Apply lifecycle rules to the B2 bucket.\n\nSets auto-expiry on recordings/ prefix (default 90 days).\nVault files (vault/ prefix) are exempt — they persist until user deletes.\n\nIdempotent: safe to call multiple times.","operationId":"configure_b2_lifecycle_api_v1_system_storage_b2_lifecycle_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/storage/b2/vault/{auth_user_id}":{"get":{"tags":["system"],"summary":"Get User Vault Usage","description":"Get vault storage usage for a specific user (admin view).","operationId":"get_user_vault_usage_api_v1_system_storage_b2_vault__auth_user_id__get","parameters":[{"name":"auth_user_id","in":"path","required":true,"schema":{"type":"string","title":"Auth User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/autoscaler":{"get":{"tags":["system"],"summary":"Get Autoscaler Status","description":"Return the latest autoscaler input snapshot, applied decision, and thresholds.\n\nRead-only — used by the Command Center to surface why monitoring concurrency\nwas raised or lowered. The decision itself is computed by ``AutoscalerWorker``\non a fixed interval (``AUTOSCALER_CHECK_INTERVAL_SECONDS``).","operationId":"get_autoscaler_status_api_v1_system_autoscaler_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/usdt-watcher":{"get":{"tags":["system"],"summary":"Get Usdt Watcher Status","description":"Return the USDT payment watcher's live snapshot + ledger counts.\n\nRead-only — surfaces the poll watermark, cadence mode and the\ncrypto_payments_seen status breakdown (the ``unmatched`` count is the\nmanual-review queue; runbook in docs/BITCART_OPS.md).","operationId":"get_usdt_watcher_status_api_v1_system_usdt_watcher_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/system/crypto-payments":{"get":{"tags":["system"],"summary":"List Crypto Payments","description":"List crypto payment ledger rows in a given status (default ``unmatched``).\n\nThe ``unmatched`` queue is the manual-review backlog (runbook in\ndocs/BITCART_OPS.md): transfers the watcher could not settle automatically\n(no invoice, ambiguous match, or a fee-shaved amount that misses the exact\nfingerprint). ``unmatched_total`` sums everything still awaiting settlement\n(``unmatched`` + ``matched``) so ops can see the outstanding exposure.","operationId":"list_crypto_payments_api_v1_system_crypto_payments_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"unmatched","title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/crypto-payments/{txid}/assign":{"post":{"tags":["system"],"summary":"Assign Crypto Payment","description":"Manually assign a transfer to an invoice and settle it via the real path.\n\nReuses ``bitcart_service.settle_manual`` (mark_complete → gated activation →\nledger ``settled``); accepts ANY invoice_id since fee-shaved amounts are\nnever auto-suggested. A missing/non-assignable invoice or a refused\nactivation surfaces as 400 — never a silent status flip.","operationId":"assign_crypto_payment_api_v1_system_crypto_payments__txid__assign_post","parameters":[{"name":"txid","in":"path","required":true,"schema":{"type":"string","title":"Txid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignPaymentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Not assignable / activation refused"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/system/crypto-payments/{txid}/ignore":{"post":{"tags":["system"],"summary":"Ignore Crypto Payment","description":"Dismiss a transfer from the review queue (marks it ``ignored``).\n\nPure ledger bookkeeping — no settlement. Use for dust, double-recorded\ntransfers, or amounts that will never map to an invoice.","operationId":"ignore_crypto_payment_api_v1_system_crypto_payments__txid__ignore_post","parameters":[{"name":"txid","in":"path","required":true,"schema":{"type":"string","title":"Txid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IgnorePaymentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/telegram":{"post":{"tags":["auth"],"summary":"Login Telegram","description":"Authenticate via Telegram Login Widget.\n\nValidates the HMAC-SHA256 signed payload from Telegram,\ncreates or updates the user, and issues JWT + refresh token.","operationId":"login_telegram_api_v1_auth_telegram_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelegramLoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"Invalid Telegram payload"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/magic-link":{"post":{"tags":["auth"],"summary":"Request Magic Link","description":"Request a magic link email for passwordless login.\n\nAlways returns the same response regardless of whether the email\nexists or is whitelisted (prevents user enumeration).","operationId":"request_magic_link_api_v1_auth_magic_link_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/verify-magic-link":{"post":{"tags":["auth"],"summary":"Verify Magic Link","description":"Verify a magic link token and issue JWT + refresh token.\n\nMobile-originated links (those with ``session_id`` set on the magic-link\nrow, see #138) skip the web-session side effects: the verification still\nruns server-side so ``verified_access_token`` is persisted for polling,\nbut no refresh cookie is set and the access token is NOT echoed in the\nresponse. The mobile client retrieves it via GET /auth/magic-link/status.","operationId":"verify_magic_link_api_v1_auth_verify_magic_link_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AuthTokenResponse"},{"$ref":"#/components/schemas/MobileOnlyVerifyResponse"}],"title":"Response Verify Magic Link Api V1 Auth Verify Magic Link Post"}}}},"401":{"description":"Invalid, expired, or used magic link"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/verify-code":{"post":{"tags":["auth"],"summary":"Verify Code","description":"Verify a one-time login code (OTP) and issue JWT + refresh token (#260).\n\nThe code is emailed alongside the magic link. Mobile clients submit the\ncode plus the ``session_id`` they generated for the magic-link request and\nget both tokens back in the body — no deep-link redirect, no polling. The\nservice enforces single-use, a per-code attempt cap, and constant-time\ncomparison; all failures surface as a generic 401 (anti-enumeration).","operationId":"verify_code_api_v1_auth_verify_code_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyCodeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"Invalid, expired, used, or locked code"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/magic-link/status":{"get":{"tags":["auth"],"summary":"Get Magic Link Status","description":"Public endpoint — poll for magic-link verification completion.\n\nMobile clients generate a session_id, send it with POST /auth/magic-link,\nthen poll this endpoint until ``status == \"verified\"`` to retrieve the\naccess token. Decouples login from the deep-link redirect, which is\nfragile via Gmail click-tracking and Chrome iOS quirks.\n\nAlways returns 200 (including ``not_found``) so polling clients have\nconsistent semantics. Never returns email, user_id, or other PII.\n\nToken gating (J-003 / #262): the client-chosen ``session_id`` is NOT a\nsecret (Math.random on mobile; also leakable via query-string logs, proxies,\nReferer), so it alone must never buy a durable, full-account bearer. BOTH the\nstored long-lived (``jwt_access_token_expire_minutes``) access token AND the\n90-day refresh token are released ONLY when the caller proves possession of\nthe server-minted ``poll_token`` (constant-time compared against the stored\nhash; a NULL ``poll_token_hash`` legacy row can never release them — no\ndowngrade path).\n\nOn the session_id-only path (no/invalid ``poll_token``) the endpoint instead\nmints a throwaway, short-TTL access token (``magic_link_poll_access_ttl_seconds``,\ndefault 120s) and no refresh token: un-updated mobile clients still complete\nlogin (no hard outage) while a session_id guesser can harvest at most a\n~2-minute, non-refreshable token instead of a 7-day account takeover. Full\nclosure of J-003 requires the mobile client to send ``poll_token`` here; only\nthen can the session_id-only path be removed entirely.\n\nTreat ``poll_token`` as a credential — never log it (query strings included).","operationId":"get_magic_link_status_api_v1_auth_magic_link_status_get","parameters":[{"name":"session_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"title":"Session Id"}},{"name":"poll_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Poll Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/pin-login":{"post":{"tags":["auth"],"summary":"Pin Login","description":"Exchange a valid super-admin PIN for a JWT access token.\n\nThe issued token carries sub='pin-super-admin' and role='super_admin'.\nNo DB user is created or queried — the session is synthetic. The DB\ndependency exists only to write the login event log (#313).","operationId":"pin_login_api_v1_auth_pin_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinLoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"Invalid PIN"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh Token","description":"Rotate refresh token and issue a new access token (rolling rotation).\n\nAccepts the refresh token from the request body (mobile, no cookies) OR\nfrom the httpOnly cookie (web). Body wins when both are present.\n\nOn success: a new access token + new refresh token are issued, the old\nrefresh is revoked single-use, and the cookie is rotated. Mobile clients\nrotate by storing the new ``refresh_token`` from the response body.\n\nOn reuse of an already-revoked token (replay): the entire token family is\nrevoked as defense-in-depth (handled by the service layer).","operationId":"refresh_token_api_v1_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RefreshTokenRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokenResponse"}}}},"401":{"description":"Invalid or expired refresh token"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Revoke refresh token and clear cookie.\n\nAccepts the refresh token from the request body (mobile, no cookies) OR\nfrom the httpOnly cookie (web) — the same precedence as ``/refresh`` (#321).\nReading only the cookie meant a mobile logout revoked nothing server-side:\nthe client cleared SecureStore while its 90-day chain stayed valid, so a\ncopy of that token kept working.\n\nStill answers ``{\"success\": true}`` when no token is supplied — logging out\nis not an operation a caller should be able to probe for.","operationId":"logout_api_v1_auth_logout_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RefreshTokenRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Logout Api V1 Auth Logout Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["auth"],"summary":"Get Me","description":"Get the current authenticated user.\n\nSupports both JWT Bearer tokens and X-Pin-Auth header.\nPIN-auth returns a synthetic super_admin identity.","operationId":"get_me_api_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"401":{"description":"Not authenticated"}}},"delete":{"tags":["auth"],"summary":"Delete Own Account","description":"Delete the current user's account.\n\nFlow:\n1. Block super_admin self-deletion (would lock out admin access).\n2. Delete user from DB (cascade removes tokens, magic links).","operationId":"delete_own_account_api_v1_auth_me_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Delete Own Account Api V1 Auth Me Delete"}}}},"403":{"description":"Super admins cannot delete their own account"},"401":{"description":"Not authenticated"}}}},"/api/v1/auth/users/{user_id}/role":{"patch":{"tags":["auth"],"summary":"Update User Role","description":"Update a user's role (super_admin only).\n\nMirrors the moderator-role audit-trail invariant from\n:func:`app.api.routes.admin.admin_update_role`: any transition into or\nout of the ``moderator`` role writes a ``MOD_INVITED`` /\n``MOD_REVOKED`` audit row. This legacy auth-side endpoint must enforce\nthe same invariant so it cannot be used as a back-door around the audit\nlog.","operationId":"update_user_role_api_v1_auth_users__user_id__role_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login-status":{"get":{"tags":["auth"],"summary":"Login Status","description":"Public endpoint: check if public login (email/Telegram) is enabled.","operationId":"login_status_api_v1_auth_login_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Login Status Api V1 Auth Login Status Get"}}}}}}},"/api/v1/auth/config":{"get":{"tags":["auth"],"summary":"Get Auth Config","description":"Public endpoint: report which login methods this deployment has enabled.\n\nSafe to expose — returns only enabled flags and a non-secret bot username.\nNever returns tokens, API keys, email addresses, or other secrets.","operationId":"get_auth_config_api_v1_auth_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthConfigResponse"}}}}}}},"/api/v1/admin/users":{"post":{"tags":["admin"],"summary":"Admin Create User","description":"Create a new user account (super_admin only).\n\nWhen ``role=moderator``, a ``MOD_INVITED`` audit row is written so every\nmoderator-role change is reflected in the audit log — same invariant as\n:meth:`ModerationService.invite_moderator`. No magic-link is sent: this\nflow is for \"create with credentials directly\"; the magic-link path lives\nin the ``/mod`` invite tab.","operationId":"admin_create_user_api_v1_admin_users_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCreateUserRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"403":{"description":"Not a super admin"},"409":{"description":"Email already exists"},"422":{"description":"Invalid email format"}}},"get":{"tags":["admin"],"summary":"List Users","description":"List all authenticated users with pagination and optional filters.","operationId":"list_users_api_v1_admin_users_get","parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by role","title":"Role"},"description":"Filter by role"},{"name":"subscription_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by subscription status","title":"Subscription Status"},"description":"Filter by subscription status"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserListResponse"}}}},"403":{"description":"Not a super admin"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}":{"get":{"tags":["admin"],"summary":"Get User","description":"Get detailed user info including subscription data.","operationId":"get_user_api_v1_admin_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Admin Delete User","description":"Delete a user and revoke all their tokens.","operationId":"admin_delete_user_api_v1_admin_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Delete User Api V1 Admin Users  User Id  Delete"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/role":{"patch":{"tags":["admin"],"summary":"Admin Update Role","description":"Change a user's role.\n\nWhen the new or previous role is ``moderator``, a ``MOD_INVITED`` or\n``MOD_REVOKED`` audit row is written so every moderator-role change goes\nthrough the audit log — including promotions/demotions performed via\n``/admin`` (not just the dedicated ``ModerationService.invite_moderator``\n/ ``revoke_moderator`` paths). No magic-link is sent; the user already\nhas credentials.","operationId":"admin_update_role_api_v1_admin_users__user_id__role_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/subscription":{"patch":{"tags":["admin"],"summary":"Admin Update Subscription","description":"Manually update a user's subscription status or expiry date.","operationId":"admin_update_subscription_api_v1_admin_users__user_id__subscription_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/revoke-subscription":{"post":{"tags":["admin"],"summary":"Admin Revoke Subscription","description":"Revoke a user's subscription locally and downgrade to free.","operationId":"admin_revoke_subscription_api_v1_admin_users__user_id__revoke_subscription_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Revoke Subscription Api V1 Admin Users  User Id  Revoke Subscription Post"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"400":{"description":"No active subscription to revoke"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/monitored-creators":{"get":{"tags":["admin"],"summary":"Admin Get Monitored Creators","description":"Get TikTok creators monitored by a specific platform user.\n\nReturns each creator's username, state, monitoring status, subscriber\ncount, recording count, and when this user started monitoring them.","operationId":"admin_get_monitored_creators_api_v1_admin_users__user_id__monitored_creators_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Get Monitored Creators Api V1 Admin Users  User Id  Monitored Creators Get"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/creators/{tiktok_user_id}/subscribers":{"get":{"tags":["admin"],"summary":"Admin Get Creator Subscribers","description":"Get the platform users who monitor a given TikTok creator.\n\nInverse of ``/users/{user_id}/monitored-creators`` (#95): given a creator,\nreturn WHO sends them to record, oldest-first (first = originating\nsubscriber still subscribed). super_admin-only — this is the\n\"who-watches-whom\" surface; it must never reach non-admins (BOLA). Reads\nthe existing ``user_monitors`` graph; no durable attribution is stored.","operationId":"admin_get_creator_subscribers_api_v1_admin_creators__tiktok_user_id__subscribers_get","parameters":[{"name":"tiktok_user_id","in":"path","required":true,"schema":{"type":"string","title":"Tiktok User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorSubscribersResponse"}}}},"403":{"description":"Not a super admin"},"404":{"description":"Creator not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/stats":{"get":{"tags":["admin"],"summary":"Admin Stats","description":"Get dashboard statistics: user counts by role, storage usage.","operationId":"admin_stats_api_v1_admin_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminStatsResponse"}}}},"403":{"description":"Not a super admin"}}}},"/api/v1/admin/abuse/flagged":{"get":{"tags":["admin"],"summary":"Admin List Flagged Users","description":"List all users with an abuse flag, including their linked accounts.","operationId":"admin_list_flagged_users_api_v1_admin_abuse_flagged_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Admin List Flagged Users Api V1 Admin Abuse Flagged Get"}}}},"403":{"description":"Not a super admin"}}}},"/api/v1/admin/abuse/{user_id}/clear":{"patch":{"tags":["admin"],"summary":"Admin Clear Abuse Flag","description":"Clear an abuse flag from a user (admin override).","operationId":"admin_clear_abuse_flag_api_v1_admin_abuse__user_id__clear_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Clear Abuse Flag Api V1 Admin Abuse  User Id  Clear Patch"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/abuse/{user_id}/flag":{"patch":{"tags":["admin"],"summary":"Admin Set Abuse Flag","description":"Manually flag a user for abuse (admin override).","operationId":"admin_set_abuse_flag_api_v1_admin_abuse__user_id__flag_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"reason","in":"query","required":false,"schema":{"type":"string","default":"manual","title":"Reason"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Set Abuse Flag Api V1 Admin Abuse  User Id  Flag Patch"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/abuse/{user_id}":{"get":{"tags":["admin"],"summary":"Admin Get User Abuse Details","description":"Per-user abuse details for the admin Flags modal.","operationId":"admin_get_user_abuse_details_api_v1_admin_abuse__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Get User Abuse Details Api V1 Admin Abuse  User Id  Get"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/test-user":{"patch":{"tags":["admin"],"summary":"Admin Set Test User","description":"Mark/unmark a user as a test account (bypasses abuse enforcement).","operationId":"admin_set_test_user_api_v1_admin_users__user_id__test_user_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestUserUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"403":{"description":"Not a super admin"},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/login-events":{"get":{"tags":["admin"],"summary":"Admin List Login Events","description":"List authentication attempts, newest first (super_admin only).\n\nSuccesses and failed PIN attempts both appear here; the email\nnotification (#311) covers successes only.","operationId":"admin_list_login_events_api_v1_admin_login_events_get","parameters":[{"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":"only_failures","in":"query","required":false,"schema":{"type":"boolean","description":"Show only failed attempts","default":false,"title":"Only Failures"},"description":"Show only failed attempts"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginEventsResponse"}}}},"403":{"description":"Not a super admin"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/webhooks/bitcart":{"post":{"tags":["webhooks"],"summary":"Bitcart Ipn","description":"Handle Bitcart IPN notifications.\n\nAlways returns 200 to avoid Bitcart's retry storm; errors are\nlogged for manual replay.","operationId":"bitcart_ipn_api_v1_webhooks_bitcart_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Bitcart Ipn Api V1 Webhooks Bitcart Post"}}}}}}},"/api/v1/billing/crypto-checkout":{"post":{"tags":["billing"],"summary":"Crypto Checkout","description":"Create a Bitcart invoice for crypto payment.\n\nFlow:\n1. Frontend calls POST /billing/crypto-checkout with { plan, billing_cycle, payment_method }\n2. Backend creates a Bitcart invoice locked to the chosen coin's wallet\n3. Returns { payment_url } — frontend opens the in-app modal with the invoice\n\nRate-limited to 5/hour per IP (Janus Q3) to prevent TronGrid quota burn\nattacks via repeated invoice creation.","operationId":"crypto_checkout_api_v1_billing_crypto_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoCheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoCheckoutResponse"}}}},"401":{"description":"Unauthorized"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/crypto-checkout-custom":{"post":{"tags":["billing"],"summary":"Crypto Checkout Custom","description":"Create a Bitcart invoice for a custom USD amount.\n\nPeriod is derived at activation time from the invoice price:\n``period_days = floor(amount_usd / 9.99 * 30)`` — base monthly rate.\nBundled SKUs (1mo/3mo/12mo) carry visible discounts; custom amounts run\nat base rate so picking a fixed plan always saves more.\n\nRate-limited to 5/hour per IP (Janus Q3).","operationId":"crypto_checkout_custom_api_v1_billing_crypto_checkout_custom_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomCryptoCheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoCheckoutResponse"}}}},"400":{"description":"Invalid amount"},"401":{"description":"Unauthorized"},"429":{"description":"Rate limit exceeded"},"500":{"description":"Internal server error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/invoice-status/{invoice_id}":{"get":{"tags":["billing"],"summary":"Get Invoice Status","description":"Read-through proxy of Bitcart's invoice status. Used by the\ncrypto-checkout modal to poll for completion without exposing the\nBitcart API directly to the browser.\n\nAuthorization: order_id format is ``pro_{cycle}:{user_id}:{email}``;\nwe verify the requesting user owns this invoice before returning.","operationId":"get_invoice_status_api_v1_billing_invoice_status__invoice_id__get","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"403":{"description":"Invoice does not belong to current user"},"404":{"description":"Invoice not found"},"500":{"description":"Bitcart API error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/admin/invoices":{"get":{"tags":["billing"],"summary":"Admin List Invoices","description":"List Bitcart store invoices for the admin billing view — server-side\npaginated, searchable, and sortable across the WHOLE store (#259).\n\nBecause Bitcart's own filtering is unreliable (#179), search and sort must\nspan every invoice, not a single upstream page. So we pull the full store\nset (bounded by ``_ADMIN_INVOICES_FETCH_CAP``), project each row through\n``summarize_admin_invoice`` (which whitelists presentation fields and\ndefensively parses user_id/email out of the order_id), then filter → sort →\nslice in memory.\n\nParams:\n  * ``page`` / ``page_size`` — 1-based pagination. ``limit`` is accepted as\n    a legacy alias for ``page_size`` (the live frontend still sends\n    ``?limit=50``); it only applies when ``page_size`` is left at its\n    default so an explicit ``page_size`` always wins.\n  * ``q`` — case-insensitive substring over email / user_id / id / order_id.\n  * ``status`` — filter on the summarized status (we DO filter here; the\n    upstream hint can't be trusted).\n  * ``sort_by`` / ``sort_dir`` — whitelisted column + asc|desc, else 400.\n\nResponse (additive — the live frontend keeps reading ``invoices``):\n``{ invoices, total, page, page_size, truncated }`` where ``truncated`` is\nTrue iff the fetch hit the cap (more invoices exist upstream than we\nscanned) — never silently dropped.","operationId":"admin_list_invoices_api_v1_billing_admin_invoices_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"1-based page index into the filtered/sorted result.","default":1,"title":"Page"},"description":"1-based page index into the filtered/sorted result."},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Rows per page (1..100).","default":25,"title":"Page Size"},"description":"Rows per page (1..100)."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":200,"minimum":1},{"type":"null"}],"description":"Legacy alias for page_size (the currently-deployed frontend sends ?limit=). Used when page_size is left at its default.","title":"Limit"},"description":"Legacy alias for page_size (the currently-deployed frontend sends ?limit=). Used when page_size is left at its default."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive substring matched against email, user_id, id, and raw order_id (spans ALL invoices).","title":"Q"},"description":"Case-insensitive substring matched against email, user_id, id, and raw order_id (spans ALL invoices)."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Server-side filter on the summarized status (Bitcart ignores it upstream, #179).","title":"Status"},"description":"Server-side filter on the summarized status (Bitcart ignores it upstream, #179)."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"One of: created | expiration | price | status | coin | email | user_id | id.","default":"created","title":"Sort By"},"description":"One of: created | expiration | price | status | coin | email | user_id | id."},{"name":"sort_dir","in":"query","required":false,"schema":{"type":"string","description":"asc | desc.","default":"desc","title":"Sort Dir"},"description":"asc | desc."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid sort_by / sort_dir"},"403":{"description":"Not a super admin"},"503":{"description":"Crypto payments are not configured"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/products":{"get":{"tags":["billing"],"summary":"List Products","description":"Return available products with pricing for the frontend.\n\nPublic endpoint — no auth required. Product IDs are kept stable strings\nso the frontend can identify a billing cycle without depending on a\nthird-party catalog.","operationId":"list_products_api_v1_billing_products_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/billing/payment-methods/availability":{"get":{"tags":["billing"],"summary":"Payment Methods Availability","description":"Public probe: which payment methods are currently usable?\n\nReturns: ``{methods: {\"BTC\": \"online\"|\"offline\", \"USDT-TRC20\": \"online\"|\"offline\"}, ttl_seconds: int}``\n\nNo auth — safe to be public. The frontend grid uses this on mount to\nrender unavailable methods in disabled state (instead of letting users\npay into a coin whose daemon is mid-restart).","operationId":"payment_methods_availability_api_v1_billing_payment_methods_availability_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/billing/crypto-interest":{"get":{"tags":["billing"],"summary":"Get Crypto Interest","description":"Return crypto interest count. Available to any authenticated user.","operationId":"get_crypto_interest_api_v1_billing_crypto_interest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["billing"],"summary":"Register Crypto Interest","description":"Register current user's interest in crypto payments.","operationId":"register_crypto_interest_api_v1_billing_crypto_interest_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/billing/cancel-subscription":{"post":{"tags":["billing"],"summary":"Cancel Subscription","description":"Cancel the authenticated user's subscription locally.\n\nMarks the local subscription as CANCELLED so it stops auto-renewing\non our side. There is no upstream payment-processor cancellation\nbecause the card-processor integration was removed; crypto invoices\nare one-shot payments and require no cancellation API call.","operationId":"cancel_subscription_api_v1_billing_cancel_subscription_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CancelSubscriptionRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/reactivate-subscription":{"post":{"tags":["billing"],"summary":"Reactivate Subscription","description":"Undo a pending cancellation locally — re-enable auto-renew flag.\n\nOnly valid when the subscription was cancelled but hasn't expired yet.\nNo upstream payment-processor call.","operationId":"reactivate_subscription_api_v1_billing_reactivate_subscription_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/dmca":{"post":{"tags":["dmca"],"summary":"Submit a DMCA takedown request (public, rate-limited)","description":"Public endpoint: anyone can submit a DMCA takedown request.","operationId":"submit_dmca_request_api_v1_dmca_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DmcaSubmitRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Submit Dmca Request Api V1 Dmca Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["dmca"],"summary":"List DMCA requests (admin only)","description":"List all DMCA requests, optionally filtered by status.","operationId":"list_dmca_requests_api_v1_dmca_get","parameters":[{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status Filter"},"description":"Filter by status"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Dmca Requests Api V1 Dmca Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dmca/optout":{"post":{"tags":["dmca"],"summary":"Submit a creator self-service opt-out request (public, rate-limited)","description":"Public endpoint: creators can opt out of monitoring without filing DMCA.\n\nBacked by the same DmcaRequest table (url_type=\"opt_out\") so admins\nprocess opt-outs through the existing review pipeline. v1 is\nmanual-review only; auto-removal is a follow-up issue.","operationId":"submit_opt_out_request_api_v1_dmca_optout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptOutSubmitRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Opt Out Request Api V1 Dmca Optout Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dmca/pending-count":{"get":{"tags":["dmca"],"summary":"Count pending DMCA requests (admin only)","description":"Quick count of outstanding requests for badge display.\n\nCounts PENDING **and** REVIEWING (#305): a request that has been picked up\nis still inside the published 48-hour review window. ``pending`` is kept as\na deprecated alias of ``open`` so the badge keeps working across an\nuncoordinated backend/frontend deploy — the two ship on separate pipelines.","operationId":"get_pending_count_api_v1_dmca_pending_count_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Pending Count Api V1 Dmca Pending Count Get"}}}}}}},"/api/v1/dmca/{request_id}":{"get":{"tags":["dmca"],"summary":"Get a single DMCA request (admin only)","description":"Get details of a specific DMCA request.","operationId":"get_dmca_request_api_v1_dmca__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Dmca Request Api V1 Dmca  Request Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["dmca"],"summary":"Update DMCA request status (admin only)","description":"Resolve or reject a DMCA request.","operationId":"update_dmca_status_api_v1_dmca__request_id__patch","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DmcaStatusUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Dmca Status Api V1 Dmca  Request Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dmca/remove-recording":{"post":{"tags":["dmca"],"summary":"DMCA-remove a specific recording (admin only)","description":"Set a recording to DMCA_REMOVED (hidden, deleted after 30 days).","operationId":"dmca_remove_recording_api_v1_dmca_remove_recording_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DmcaRemoveRecordingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Dmca Remove Recording Api V1 Dmca Remove Recording Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dmca/ban-and-remove":{"post":{"tags":["dmca"],"summary":"Ban TikTok user + DMCA-remove all their recordings (admin only)","description":"Deactivate a TikTok user and DMCA-remove all their recordings.","operationId":"dmca_ban_and_remove_api_v1_dmca_ban_and_remove_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DmcaBanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Dmca Ban And Remove Api V1 Dmca Ban And Remove Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/quarantine/{recording_id}":{"post":{"tags":["moderation"],"summary":"Move a recording to QUARANTINED visibility (moderator+)","description":"Quarantine a recording for peer review. Single-mod action.","operationId":"quarantine_recording_api_v1_moderation_quarantine__recording_id__post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReasonedActionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Quarantine Recording Api V1 Moderation Quarantine  Recording Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/release/{recording_id}":{"post":{"tags":["moderation"],"summary":"Lift quarantine, revert to UNLISTED (moderator+)","operationId":"release_quarantine_api_v1_moderation_release__recording_id__post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteOnlyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Release Quarantine Api V1 Moderation Release  Recording Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/hide/{recording_id}":{"post":{"tags":["moderation"],"summary":"Set visibility to PRIVATE via moderation action (moderator+)","operationId":"hide_recording_api_v1_moderation_hide__recording_id__post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReasonedActionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Hide Recording Api V1 Moderation Hide  Recording Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/unhide/{recording_id}":{"post":{"tags":["moderation"],"summary":"Revert a moderation HIDE back to UNLISTED (moderator+)","operationId":"unhide_recording_api_v1_moderation_unhide__recording_id__post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteOnlyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Unhide Recording Api V1 Moderation Unhide  Recording Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/vote-delete/{recording_id}":{"post":{"tags":["moderation"],"summary":"Cast a deletion vote (two distinct mod votes required to hard-delete)","description":"Record a vote to permanently delete a quarantined recording.\n\nWhen the second distinct (and non-retracted) vote arrives, and neither\nvoter is the quarantining moderator, the recording is hard-deleted and\na DELETE_EXECUTED audit row is written.","operationId":"vote_to_delete_api_v1_moderation_vote_delete__recording_id__post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReasonedActionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Vote To Delete Api V1 Moderation Vote Delete  Recording Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/retract-vote/{recording_id}":{"post":{"tags":["moderation"],"summary":"Retract your previous deletion vote (audited)","operationId":"retract_vote_api_v1_moderation_retract_vote__recording_id__post","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteOnlyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Retract Vote Api V1 Moderation Retract Vote  Recording Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/queue":{"get":{"tags":["moderation"],"summary":"Paginated list of QUARANTINED recordings with vote tallies","operationId":"get_queue_api_v1_moderation_queue_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Queue Api V1 Moderation Queue Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/hidden":{"get":{"tags":["moderation"],"summary":"Paginated list of recordings hidden via moderator action","operationId":"get_hidden_api_v1_moderation_hidden_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Hidden Api V1 Moderation Hidden Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/audit-log":{"get":{"tags":["moderation"],"summary":"Read-only moderation audit log (any moderator can read)","operationId":"get_audit_log_api_v1_moderation_audit_log_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"actor_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Id"}},{"name":"action_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Type"}},{"name":"recording_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Audit Log Api V1 Moderation Audit Log Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/my-vote/{recording_id}":{"get":{"tags":["moderation"],"summary":"Get the calling moderator's active vote on a recording, if any","operationId":"get_my_vote_api_v1_moderation_my_vote__recording_id__get","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get My Vote Api V1 Moderation My Vote  Recording Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/invite":{"post":{"tags":["moderation"],"summary":"Invite a new moderator via magic link (super_admin only)","description":"Create or upgrade a user to role=moderator and send the login email.\n\nNo-op upgrade when the email already maps to a super_admin (we don't\ndowngrade existing super_admins by mistake). Audit row is written in\nevery case so the operator-facing log captures the intent.\n\nThe response intentionally omits ``previous_role`` (J-003): that field\nleaks a user's payment tier to any operator who can reach this endpoint.\nThe audit row in ``moderation_actions`` retains it for forensics.","operationId":"invite_moderator_api_v1_moderation_invite_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteModeratorRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Invite Moderator Api V1 Moderation Invite Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/moderation/revoke/{user_id}":{"post":{"tags":["moderation"],"summary":"Revoke moderator role from a user, downgrading to free (super_admin only)","description":"Strip the moderator role from a user.\n\nAs with invite, the response omits ``previous_role``; only the audit\nrow retains it.","operationId":"revoke_moderator_api_v1_moderation_revoke__user_id__post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Moderator Api V1 Moderation Revoke  User Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips":{"post":{"tags":["clips"],"summary":"Create Clip","description":"Create (enqueue) a clip from one of the user's accessible recordings.","operationId":"create_clip_api_v1_clips_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipResponse"}}}},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Forbidden"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["clips"],"summary":"List Clips","description":"List the current user's clips, newest first.","operationId":"list_clips_api_v1_clips_get","parameters":[{"name":"recording_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by source recording","title":"Recording Id"},"description":"Filter by source recording"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ClipResponse"},"title":"Response List Clips Api V1 Clips Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/{clip_id}":{"get":{"tags":["clips"],"summary":"Get Clip","operationId":"get_clip_api_v1_clips__clip_id__get","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["clips"],"summary":"Rename Clip","operationId":"rename_clip_api_v1_clips__clip_id__patch","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipRenameRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["clips"],"summary":"Delete Clip","operationId":"delete_clip_api_v1_clips__clip_id__delete","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Clip Api V1 Clips  Clip Id  Delete"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/{clip_id}/share":{"post":{"tags":["clips"],"summary":"Enable Share","description":"Enable a clip's public share link. Paid tiers only (backend-enforced).","operationId":"enable_share_api_v1_clips__clip_id__share_post","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipResponse"}}}},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["clips"],"summary":"Disable Share","description":"Disable a clip's public share link (idempotent).","operationId":"disable_share_api_v1_clips__clip_id__share_delete","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/{clip_id}/stream":{"get":{"tags":["clips"],"summary":"Stream Clip","description":"Stream a clip for inline playback (owner only).","operationId":"stream_clip_api_v1_clips__clip_id__stream_get","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/{clip_id}/file":{"get":{"tags":["clips"],"summary":"Download Clip","description":"Download a clip file (owner only).","operationId":"download_clip_api_v1_clips__clip_id__file_get","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/{clip_id}/thumbnail":{"get":{"tags":["clips"],"summary":"Clip Thumbnail","description":"Serve a clip's thumbnail (owner only).","operationId":"clip_thumbnail_api_v1_clips__clip_id__thumbnail_get","parameters":[{"name":"clip_id","in":"path","required":true,"schema":{"type":"string","title":"Clip Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/public/{slug}":{"get":{"tags":["clips"],"summary":"Get Public Clip","description":"Public clip metadata for the share page + Open Graph tags (no auth).","operationId":"get_public_clip_api_v1_clips_public__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClipPublicResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/public/{slug}/stream":{"get":{"tags":["clips"],"summary":"Stream Public Clip","description":"Stream a public clip (no auth) — used by the share page player + OG video.","operationId":"stream_public_clip_api_v1_clips_public__slug__stream_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/clips/public/{slug}/thumbnail":{"get":{"tags":["clips"],"summary":"Public Clip Thumbnail","description":"Serve a public clip's thumbnail (no auth) — used as OG image.","operationId":"public_clip_thumbnail_api_v1_clips_public__slug__thumbnail_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications/preferences":{"get":{"tags":["notifications"],"summary":"Get Preferences","description":"Get the caller's category toggles. Every category defaults to on.","operationId":"get_preferences_api_v1_notifications_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}}}}},"patch":{"tags":["notifications"],"summary":"Update Preferences","description":"Update some or all of the caller's category toggles.\n\nOmitted categories are left untouched, so the settings UI can save one\nswitch at a time without shipping the whole set.","operationId":"update_preferences_api_v1_notifications_preferences_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications":{"get":{"tags":["notifications"],"summary":"List Notifications","description":"The caller's newest notifications, plus the unread badge count.","operationId":"list_notifications_api_v1_notifications_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max rows to return","default":50,"title":"Limit"},"description":"Max rows to return"},{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only unread rows","default":false,"title":"Unread Only"},"description":"Only unread rows"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["notifications"],"summary":"Clear Notifications","description":"Delete the caller's whole feed.","operationId":"clear_notifications_api_v1_notifications_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Clear Notifications Api V1 Notifications Delete"}}}}}}},"/api/v1/notifications/read-all":{"post":{"tags":["notifications"],"summary":"Mark All Read","description":"Mark every unread notification of the caller as read.","operationId":"mark_all_read_api_v1_notifications_read_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Mark All Read Api V1 Notifications Read All Post"}}}}}}},"/api/v1/notifications/{notification_id}/read":{"patch":{"tags":["notifications"],"summary":"Mark Read","description":"Mark one of the caller's notifications as read. Idempotent.","operationId":"mark_read_api_v1_notifications__notification_id__read_patch","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications/{notification_id}":{"delete":{"tags":["notifications"],"summary":"Dismiss Notification","description":"Delete one of the caller's notifications.","operationId":"dismiss_notification_api_v1_notifications__notification_id__delete","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dismiss Notification Api V1 Notifications  Notification Id  Delete"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/pulse":{"get":{"tags":["public"],"summary":"Public Pulse","description":"Coarse platform totals for the public landing ledger. No auth.\n\nThis endpoint is public by signature: it takes only ``request`` and ``db``,\nand this codebase has no router-level ``dependencies=`` and no auth\nmiddleware, so a signature carrying no ``require_role``/``get_current_user``\nIS the decision to make it anonymous. Adding an auth dependency here would\nbreak the landing page, which calls it before the visitor has an account.\n\n``request`` is not unused — slowapi resolves ``key_func`` off it and raises\nat call time if the handler does not accept a parameter with that name.","operationId":"public_pulse_api_v1_public_pulse_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPulseResponse"}}}}}}},"/api/v1/visits/beacon":{"post":{"tags":["public"],"summary":"Visit Beacon","description":"Record one anonymous funnel event.","operationId":"visit_beacon_api_v1_visits_beacon_post","responses":{"204":{"description":"Successful Response"}}}},"/api/v1/admin/visits/summary":{"get":{"tags":["admin"],"summary":"Visit Summary","description":"Headline figures for the window. super_admin only.\n\n``accounts_created`` is the source of truth and ``captured_attributed`` is\nthe measured subset of it; ``unattributed`` is the gap and is expected to\nbe non-zero forever (direct /login bookmarks, mobile signups, the pricing\nauth-gate). Publishing the gap rather than hiding it is what lets the\noperator tell \"attribution is lossy\" from \"attribution is broken\".","operationId":"visit_summary_api_v1_admin_visits_summary_get","parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":30,"title":"Window Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/visits/breakdowns":{"get":{"tags":["admin"],"summary":"Visit Breakdowns","description":"Where visits come from: country, page, referring host and language.\n\nCountries, sources and locales are counted per VISIT; pages are counted per\nEVENT, because \"which pages get looked at\" is a different question from\n\"how many visits touched this page\", and collapsing pages to one row per\nvisit would silently answer the wrong one.","operationId":"visit_breakdowns_api_v1_admin_visits_breakdowns_get","parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":30,"title":"Window Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitBreakdownsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/visits/funnel":{"get":{"tags":["admin"],"summary":"Visit Funnel","description":"Arrivals through to accounts, plus depth-within-visit.\n\n``depth`` is pages per visit, NOT return frequency. Visitor identity dies\nwith the tab, so a returning-visitor number does not exist and no field\nhere should ever be rendered as one.","operationId":"visit_funnel_api_v1_admin_visits_funnel_get","parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":30,"title":"Window Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitFunnelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/visits/recent":{"get":{"tags":["admin"],"summary":"Visit Recent","description":"The raw tail, newest first.\n\nExists so an aggregate that looks wrong can be checked against the rows it\ncame from without opening psql -- and so the PK index has a real reader\nrather than a hypothetical one.","operationId":"visit_recent_api_v1_admin_visits_recent_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VisitEventRow"},"title":"Response Visit Recent Api V1 Admin Visits Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ping":{"get":{"summary":"Ping","description":"Zero-dependency liveness probe for uptime monitors.","operationId":"ping_ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Ping","description":"Zero-dependency liveness probe for uptime monitors.","operationId":"ping_ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AddProxyRequest":{"properties":{"proxy_url":{"type":"string","title":"Proxy Url","description":"SOCKS5 proxy URL (e.g., socks5://1.2.3.4:1080)"}},"type":"object","required":["proxy_url"],"title":"AddProxyRequest","description":"Request to add a SOCKS5 proxy."},"AddSessionRequest":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Human-readable label, e.g. 'Account 1'"},"session_id":{"type":"string","title":"Session Id","description":"TikTok sessionid cookie value"},"ms_token":{"type":"string","title":"Ms Token","description":"TikTok msToken cookie value","default":""}},"type":"object","required":["session_id"],"title":"AddSessionRequest","description":"Request model for adding a session.","example":{"label":"Account 1","ms_token":"BbNR0c...","session_id":"304c1fd66d27b02de1c61485afae9fdd"}},"AdminCreateUserRequest":{"properties":{"email":{"type":"string","maxLength":254,"minLength":5,"title":"Email","description":"Email address"},"display_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Display Name","description":"Display name"},"role":{"type":"string","pattern":"^(super_admin|moderator|pro|starter|free)$","title":"Role","description":"Initial role","default":"free"},"subscription_status":{"anyOf":[{"type":"string","pattern":"^(active|cancelled|past_due|expired|trialing)$"},{"type":"null"}],"title":"Subscription Status"},"subscription_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Subscription Expires At"}},"type":"object","required":["email"],"title":"AdminCreateUserRequest","description":"Admin request to create a new user account."},"AdminSettingsResponse":{"properties":{"public_login_enabled":{"type":"boolean","title":"Public Login Enabled","description":"Whether public login (email/Telegram) is enabled"},"login_notifications_enabled":{"type":"boolean","title":"Login Notifications Enabled","description":"Whether the operator is emailed on every successful login"}},"type":"object","required":["public_login_enabled","login_notifications_enabled"],"title":"AdminSettingsResponse","description":"Runtime settings controllable by the super admin."},"AdminSettingsUpdateRequest":{"properties":{"public_login_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Public Login Enabled","description":"Enable or disable public login"},"login_notifications_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Login Notifications Enabled","description":"Enable or disable the login notification emails"}},"type":"object","title":"AdminSettingsUpdateRequest","description":"Partial update for runtime admin settings."},"AdminStatsResponse":{"properties":{"total_users":{"type":"integer","title":"Total Users"},"users_by_role":{"additionalProperties":{"type":"integer"},"type":"object","title":"Users By Role"},"active_subscriptions":{"type":"integer","title":"Active Subscriptions"},"storage_used_gb":{"type":"number","title":"Storage Used Gb"}},"type":"object","required":["total_users","users_by_role","active_subscriptions","storage_used_gb"],"title":"AdminStatsResponse","description":"Dashboard statistics for admin panel."},"AdminUserListResponse":{"properties":{"users":{"items":{"$ref":"#/components/schemas/AuthUserResponse"},"type":"array","title":"Users"},"total":{"type":"integer","title":"Total"},"skip":{"type":"integer","title":"Skip"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["users","total","skip","limit"],"title":"AdminUserListResponse","description":"Paginated user list for admin panel."},"AssignPaymentRequest":{"properties":{"invoice_id":{"type":"string","title":"Invoice Id","description":"Bitcart invoice id to settle against"}},"type":"object","required":["invoice_id"],"title":"AssignPaymentRequest","description":"Manually bind an on-chain transfer to an invoice and settle it."},"AuthConfigResponse":{"properties":{"public_login_enabled":{"type":"boolean","title":"Public Login Enabled","description":"Whether public (non-PIN) login is currently enabled"},"methods":{"items":{"$ref":"#/components/schemas/AuthMethodConfig"},"type":"array","title":"Methods","description":"List of supported login methods and their enabled state"},"legal_docs_version":{"type":"string","title":"Legal Docs Version","description":"Published version of the ToS/Privacy documents (#299). Clients compare it against a user's recorded consent to detect when re-consent is required.","default":""}},"type":"object","required":["public_login_enabled","methods"],"title":"AuthConfigResponse","description":"Public auth configuration — which login methods this deployment exposes."},"AuthMethodConfig":{"properties":{"id":{"type":"string","title":"Id","description":"Stable method identifier (pin, magic_link, telegram)"},"label":{"type":"string","title":"Label","description":"Human-readable label for UI display"},"enabled":{"type":"boolean","title":"Enabled","description":"Whether this method is configured on the server"},"bot_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bot Username","description":"Telegram bot username (only set for the 'telegram' method when configured)"}},"type":"object","required":["id","label","enabled"],"title":"AuthMethodConfig","description":"Configuration for a single login method."},"AuthTokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In","description":"Token TTL in seconds"},"abuse_cookie_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Abuse Cookie Id","description":"Device correlation cookie ID for abuse prevention"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token","description":"Refresh token for clients without cookies (mobile). Web clients should ignore this field and use the httpOnly cookie. Mobile stores this in SecureStore and rotates it on each refresh."},"poll_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poll Token","description":"Opaque poll secret (populated by /verify-code). Send it as the `poll_token` query param to GET /auth/magic-link/status to receive the refresh token. Treat as a credential — do not log."}},"type":"object","required":["access_token","expires_in"],"title":"AuthTokenResponse","description":"JWT access token response."},"AuthUserResponse":{"properties":{"id":{"type":"string","title":"Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"role":{"type":"string","title":"Role","default":"free"},"telegram_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Telegram Username"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"subscription_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription Status"},"subscription_plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription Plan"},"subscription_billing_cycle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription Billing Cycle"},"subscription_external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription External Id"},"subscription_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Subscription Expires At"},"storage_quota_gb":{"type":"integer","title":"Storage Quota Gb","default":0},"abuse_flag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Abuse Flag"},"abuse_flagged_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Abuse Flagged At"},"is_test_user":{"type":"boolean","title":"Is Test User","default":false},"plan":{"anyOf":[{"$ref":"#/components/schemas/PlanLimitsResponse"},{"type":"null"}]},"consent_state":{"type":"string","enum":["current","superseded","never"],"title":"Consent State","description":"Whether this user's recorded ToS/Privacy acceptance matches the published legal_docs_version (#308). 'never' = no consent_events row at all (predates capture); 'superseded' = consented to an older version. Defaults to 'current' and FAILS OPEN — /auth/me is the app's canonical session signal and must never break on this.","default":"current"}},"type":"object","required":["id"],"title":"AuthUserResponse","description":"Authenticated user info."},"BulkVisibilityUpdateRequest":{"properties":{"recording_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Recording Ids"},"visibility":{"$ref":"#/components/schemas/RecordingVisibility"}},"type":"object","required":["recording_ids","visibility"],"title":"BulkVisibilityUpdateRequest","description":"Request to bulk-update visibility for multiple recordings."},"CancelSubscriptionRequest":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"reason_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason Detail"}},"type":"object","title":"CancelSubscriptionRequest"},"ClipCreateRequest":{"properties":{"recording_id":{"type":"string","minLength":1,"title":"Recording Id","description":"Source recording id"},"start_seconds":{"type":"integer","minimum":0.0,"title":"Start Seconds","description":"Clip start offset (inclusive), seconds"},"end_seconds":{"type":"integer","exclusiveMinimum":0.0,"title":"End Seconds","description":"Clip end offset (exclusive), seconds"},"title":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Title","description":"Optional title; auto-generated if empty"},"without_watermark":{"type":"boolean","title":"Without Watermark","description":"Paid-only preference to drop the signature; ignored for free users","default":false}},"type":"object","required":["recording_id","start_seconds","end_seconds"],"title":"ClipCreateRequest","description":"Request to create a clip from a recording.\n\n``without_watermark`` is a *preference*, not a grant: the backend forces the\nLiveReplays signature on for any user whose plan lacks\n``can_export_clips_without_watermark``. Range + duration bounds are validated\nserver-side against the source recording's real duration."},"ClipPublicResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"duration":{"type":"integer","title":"Duration"},"creator_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Username"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["slug","duration","created_at"],"title":"ClipPublicResponse","description":"Public (unauthenticated) clip metadata for the share page + OG tags."},"ClipRenameRequest":{"properties":{"title":{"type":"string","maxLength":200,"minLength":1,"title":"Title"}},"type":"object","required":["title"],"title":"ClipRenameRequest","description":"Request to rename a clip."},"ClipResponse":{"properties":{"id":{"type":"string","title":"Id"},"recording_id":{"type":"string","title":"Recording Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"start_seconds":{"type":"integer","title":"Start Seconds"},"end_seconds":{"type":"integer","title":"End Seconds"},"duration":{"type":"integer","title":"Duration"},"with_signature":{"type":"boolean","title":"With Signature"},"status":{"type":"string","title":"Status"},"is_public":{"type":"boolean","title":"Is Public","default":false},"public_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Slug"},"public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Url"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"has_thumbnail":{"type":"boolean","title":"Has Thumbnail","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"processed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Processed At"},"recording_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Title"},"creator_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Username"}},"type":"object","required":["id","recording_id","start_seconds","end_seconds","duration","with_signature","status","created_at"],"title":"ClipResponse","description":"Clip detail/list item."},"CookiesResponse":{"properties":{"sessionid":{"type":"string","title":"Sessionid","description":"TikTok session ID cookie","default":""},"msToken":{"type":"string","title":"Mstoken","description":"TikTok msToken cookie","default":""},"has_cookies":{"type":"boolean","title":"Has Cookies","description":"Whether session pool has entries"}},"type":"object","required":["has_cookies"],"title":"CookiesResponse","description":"Response model for TikTok cookies (deprecated — use /sessions instead)."},"CreatorSubscriberEntry":{"properties":{"auth_user_id":{"type":"string","title":"Auth User Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"telegram_username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Telegram Username"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"role":{"type":"string","title":"Role"},"added_at":{"type":"string","format":"date-time","title":"Added At"}},"type":"object","required":["auth_user_id","email","telegram_username","display_name","role","added_at"],"title":"CreatorSubscriberEntry","description":"One platform user who monitors a creator (super_admin-only view)."},"CreatorSubscribersResponse":{"properties":{"tiktok_user_id":{"type":"string","title":"Tiktok User Id"},"username":{"type":"string","title":"Username"},"subscriber_count":{"type":"integer","title":"Subscriber Count"},"originator_auth_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Originator Auth User Id"},"subscribers":{"items":{"$ref":"#/components/schemas/CreatorSubscriberEntry"},"type":"array","title":"Subscribers"}},"type":"object","required":["tiktok_user_id","username","subscriber_count","originator_auth_user_id","subscribers"],"title":"CreatorSubscribersResponse","description":"Who monitors a given TikTok creator — inverse of #95 (super_admin-only).\n\n``subscribers`` is oldest-first; the first entry is the originating\nsubscriber *still subscribed*. A subscriber who unsubscribed leaves no\n``user_monitors`` row (CASCADE-deleted), so this is NOT the historical\noriginator (see #271 caveat)."},"CryptoCheckoutRequest":{"properties":{"plan":{"type":"string","title":"Plan","default":"pro"},"billing_cycle":{"type":"string","title":"Billing Cycle","default":"monthly"},"payment_method":{"type":"string","title":"Payment Method","default":"BTC"}},"type":"object","title":"CryptoCheckoutRequest"},"CryptoCheckoutResponse":{"properties":{"payment_url":{"type":"string","title":"Payment Url"},"invoice_id":{"type":"string","title":"Invoice Id"}},"type":"object","required":["payment_url","invoice_id"],"title":"CryptoCheckoutResponse"},"CustomCryptoCheckoutRequest":{"properties":{"amount_usd":{"type":"number","title":"Amount Usd"},"payment_method":{"type":"string","title":"Payment Method","default":"BTC"}},"type":"object","required":["amount_usd"],"title":"CustomCryptoCheckoutRequest"},"DestroyVultrRequest":{"properties":{"instance_id":{"type":"string","title":"Instance Id","description":"Vultr instance ID"}},"type":"object","required":["instance_id"],"title":"DestroyVultrRequest","description":"Request to destroy a Vultr proxy instance."},"DmcaBanRequest":{"properties":{"tiktok_user_id":{"type":"string","title":"Tiktok User Id"}},"type":"object","required":["tiktok_user_id"],"title":"DmcaBanRequest","description":"Admin request to ban a TikTok user and remove all recordings."},"DmcaRemoveRecordingRequest":{"properties":{"recording_id":{"type":"string","title":"Recording Id"}},"type":"object","required":["recording_id"],"title":"DmcaRemoveRecordingRequest","description":"Admin request to DMCA-remove a specific recording."},"DmcaStatusUpdateRequest":{"properties":{"status":{"type":"string","title":"Status"},"resolution_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Note"}},"type":"object","required":["status"],"title":"DmcaStatusUpdateRequest","description":"Admin status update body."},"DmcaSubmitRequest":{"properties":{"claimant_name":{"type":"string","title":"Claimant Name"},"claimant_email":{"type":"string","format":"email","title":"Claimant Email"},"content_url":{"type":"string","title":"Content Url"},"url_type":{"type":"string","title":"Url Type"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"signature":{"type":"string","title":"Signature"}},"type":"object","required":["claimant_name","claimant_email","content_url","url_type","signature"],"title":"DmcaSubmitRequest","description":"Public DMCA request submission body."},"ErrorResponse":{"properties":{"error":{"type":"string","title":"Error"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Error response model."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"IgnorePaymentRequest":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the transfer is ignored"}},"type":"object","title":"IgnorePaymentRequest","description":"Dismiss a transfer from the manual-review queue."},"InviteModeratorRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"InviteModeratorRequest","description":"Body for inviting a new moderator via magic link."},"LoginEventItem":{"properties":{"id":{"type":"string","title":"Id"},"method":{"type":"string","title":"Method","description":"How the person authenticated, e.g. 'PIN (super admin)'"},"identity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Identity","description":"Email, @telegram handle, or the PIN marker"},"auth_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth User Id","description":"NULL for failed attempts and PIN sessions"},"succeeded":{"type":"boolean","title":"Succeeded"},"ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 UTC"}},"type":"object","required":["id","method","identity","auth_user_id","succeeded","ip","user_agent","created_at"],"title":"LoginEventItem","description":"One authentication attempt as shown in the admin panel."},"LoginEventsResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/LoginEventItem"},"type":"array","title":"Events"},"total":{"type":"integer","title":"Total","description":"Total matching events, for pagination"}},"type":"object","required":["events","total"],"title":"LoginEventsResponse","description":"A newest-first page of login events."},"MagicLinkRequest":{"properties":{"email":{"type":"string","maxLength":254,"minLength":5,"title":"Email","description":"Email address"},"session_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Session Id","description":"Optional client-generated correlator. When present, the mobile client can poll GET /auth/magic-link/status with this value to complete login without relying on the deep-link redirect."},"origin":{"anyOf":[{"type":"string","enum":["web","mobile"]},{"type":"null"}],"title":"Origin","description":"Client platform (#165). Drives the verify-magic-link cookie-vs-mobile-pickup decision so a WEB user who taps the emailed link still gets a browser session even though a session_id is set for OTP correlation. Defaults to None — legacy behavior where session_id presence alone implies a mobile client."},"referrer":{"anyOf":[{"type":"string","pattern":"^[a-zA-Z0-9-]{1,32}$"},{"type":"null"}],"title":"Referrer","description":"Partner attribution slug from the /welcome/<slug> landing cookie. Captured once at first signup and never overwritten on later logins."},"tos_accepted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Tos Accepted","description":"Affirmative confirmation that the user has read and accepts the Terms of Service and Privacy Policy (#299). Optional while web and mobile clients roll out; when true, recorded server-side in consent_events with the published legal docs version."},"locale":{"anyOf":[{"type":"string","enum":["en","es"]},{"type":"null"}],"title":"Locale","description":"UI locale the consent text was shown in (#299)."}},"type":"object","required":["email"],"title":"MagicLinkRequest","description":"Request to send a magic link email."},"MagicLinkResponse":{"properties":{"message":{"type":"string","title":"Message"},"poll_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poll Token","description":"Opaque poll secret; send it as the `poll_token` query param to GET /auth/magic-link/status to receive the refresh token. Treat as a credential — do not log."},"confirmation_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Code","description":"Short code to DISPLAY to the user while they check their inbox (#323). The email names the same code, so the mailbox owner can tell a sign-in they started from one someone else started for their address. Not a credential and never sent back — show it, do not store it."}},"type":"object","required":["message"],"title":"MagicLinkResponse","description":"Generic magic link response (intentionally vague for security)."},"MagicLinkStatusResponse":{"properties":{"status":{"type":"string","enum":["pending","verified","expired","not_found"],"title":"Status"},"access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Token"},"token_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Type"},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token","description":"Refresh token for clients without cookies (mobile). Web clients should ignore this field and use the httpOnly cookie. Mobile stores this in SecureStore and rotates it on each refresh."},"confirmation_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Code","description":"The code this login attempt is displaying (#323), so a client that lost its local state can re-show it while still pending. Released only on a valid `poll_token` — a caller holding just the client-chosen session_id gets nothing."}},"type":"object","required":["status"],"title":"MagicLinkStatusResponse","description":"Status of a magic-link session (mobile polling).\n\nPublic payload — never includes email, user_id, or other PII. The access\ntoken is only returned once the link has been verified server-side."},"MagicLinkVerifyRequest":{"properties":{"token":{"type":"string","minLength":10,"title":"Token","description":"Magic link token from URL"}},"type":"object","required":["token"],"title":"MagicLinkVerifyRequest","description":"Request to verify a magic link token."},"MobileOnlyVerifyResponse":{"properties":{"status":{"type":"string","const":"mobile_only","title":"Status"},"message":{"type":"string","title":"Message"}},"type":"object","required":["status","message"],"title":"MobileOnlyVerifyResponse","description":"Returned by /verify-magic-link when the link was requested from mobile.\n\nThe verification still happens server-side (the link is consumed and the\naccess token is persisted on the magic_links row), but the response does\nNOT include the access token or set a refresh-token cookie. The mobile\nclient picks up the token via GET /auth/magic-link/status using the\nsession_id it generated when requesting the link."},"NoteOnlyRequest":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note"}},"type":"object","title":"NoteOnlyRequest","description":"Body for moderation actions that only capture a free-text note."},"NotificationListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/NotificationResponse"},"type":"array","title":"Items"},"unread_count":{"type":"integer","title":"Unread Count"}},"type":"object","required":["items","unread_count"],"title":"NotificationListResponse","description":"A page of the feed plus the unread count over the WHOLE feed.\n\nThe count is not derivable from ``items`` — the list is capped, the badge\nis not."},"NotificationPreferencesResponse":{"properties":{"creator_live":{"type":"boolean","title":"Creator Live"},"recording_ready":{"type":"boolean","title":"Recording Ready"},"recording_failed":{"type":"boolean","title":"Recording Failed"},"clip":{"type":"boolean","title":"Clip"},"subscription":{"type":"boolean","title":"Subscription"},"moderation":{"type":"boolean","title":"Moderation"},"system":{"type":"boolean","title":"System"},"proxy":{"type":"boolean","title":"Proxy"}},"type":"object","required":["creator_live","recording_ready","recording_failed","clip","subscription","moderation","system","proxy"],"title":"NotificationPreferencesResponse","description":"Which categories reach this account's bell. One flag per category."},"NotificationPreferencesUpdateRequest":{"properties":{"creator_live":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Creator Live"},"recording_ready":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recording Ready"},"recording_failed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recording Failed"},"clip":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Clip"},"subscription":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Subscription"},"moderation":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Moderation"},"system":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"System"},"proxy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Proxy"}},"type":"object","title":"NotificationPreferencesUpdateRequest","description":"Partial toggle update — omitted categories keep their current value."},"NotificationResponse":{"properties":{"id":{"type":"string","title":"Id"},"category":{"type":"string","title":"Category"},"severity":{"type":"string","title":"Severity"},"title":{"type":"string","title":"Title"},"message":{"type":"string","title":"Message"},"link_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link Url"},"link_external":{"type":"boolean","title":"Link External","default":false},"read":{"type":"boolean","title":"Read"},"timestamp":{"type":"string","title":"Timestamp"}},"type":"object","required":["id","category","severity","title","message","read","timestamp"],"title":"NotificationResponse","description":"One entry of an account's notification feed.\n\n``timestamp`` is a pre-formatted ISO-8601 UTC string rather than a\n``datetime`` so this model is byte-identical to the ``user_notification``\nWebSocket frame — the client parses one shape, not two, and the naive-UTC\ncolumns cannot be misread as local time anywhere along the way.\n\n``link_external`` disambiguates the click target: an in-app path goes\nthrough the router, an absolute URL (a creator's TikTok live room) opens in\na new tab. The client never has to sniff the string."},"OptOutSubmitRequest":{"properties":{"creator_username":{"type":"string","title":"Creator Username"},"contact_email":{"type":"string","format":"email","title":"Contact Email"},"is_authorized":{"type":"boolean","title":"Is Authorized"},"additional_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Additional Context"}},"type":"object","required":["creator_username","contact_email","is_authorized"],"title":"OptOutSubmitRequest","description":"Public creator opt-out submission body — simpler than DMCA, no signature."},"PinLoginRequest":{"properties":{"pin":{"type":"string","title":"Pin","description":"Super admin authentication PIN"}},"type":"object","required":["pin"],"title":"PinLoginRequest","description":"Request to exchange a PIN for a super admin JWT."},"PlanLimitsResponse":{"properties":{"max_monitored_users":{"type":"integer","title":"Max Monitored Users"},"max_concurrent_recordings":{"type":"integer","title":"Max Concurrent Recordings"},"can_download":{"type":"boolean","title":"Can Download"},"can_download_own":{"type":"boolean","title":"Can Download Own"},"can_download_any":{"type":"boolean","title":"Can Download Any"},"retention_days":{"type":"integer","title":"Retention Days"},"storage_quota_gb":{"type":"integer","title":"Storage Quota Gb"},"can_view_special_badges":{"type":"boolean","title":"Can View Special Badges"},"can_access_library":{"type":"boolean","title":"Can Access Library"},"library_playback_mode":{"type":"string","title":"Library Playback Mode"},"can_use_vault":{"type":"boolean","title":"Can Use Vault"},"can_export_clips_without_watermark":{"type":"boolean","title":"Can Export Clips Without Watermark","default":false},"can_share_clips":{"type":"boolean","title":"Can Share Clips","default":false},"label":{"type":"string","title":"Label"}},"type":"object","required":["max_monitored_users","max_concurrent_recordings","can_download","can_download_own","can_download_any","retention_days","storage_quota_gb","can_view_special_badges","can_access_library","library_playback_mode","can_use_vault","label"],"title":"PlanLimitsResponse","description":"Plan limits for the current user's tier."},"ProvisionVultrRequest":{"properties":{"region":{"type":"string","title":"Region","description":"Vultr region slug","default":"ewr"},"plan":{"type":"string","title":"Plan","description":"Vultr plan slug","default":"vc2-1c-1gb"},"label":{"type":"string","title":"Label","description":"Instance label","default":"livereplays-proxy"}},"type":"object","title":"ProvisionVultrRequest","description":"Request to provision a new proxy via Vultr."},"ProxyCycleStats":{"properties":{"success":{"type":"integer","title":"Success","default":0},"throttled":{"type":"integer","title":"Throttled","default":0},"errors":{"type":"integer","title":"Errors","default":0}},"type":"object","title":"ProxyCycleStats","description":"Per-cycle stats for a single proxy."},"ProxyHealthEntry":{"properties":{"proxy_url":{"type":"string","title":"Proxy Url","description":"Full SOCKS5 proxy URL"},"ip":{"type":"string","title":"Ip","description":"Proxy IP address"},"port":{"type":"integer","title":"Port","description":"Proxy port"},"status":{"type":"string","title":"Status","description":"Health status: healthy, degraded, or unhealthy"},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Number of consecutive failures"},"last_success":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Success","description":"Timestamp of last successful request"},"last_error":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Error","description":"Timestamp of last failed request"},"last_error_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error Type","description":"Type of last error (e.g. ConnectTimeout)"},"total_successes":{"type":"integer","title":"Total Successes","description":"Total successful requests since startup"},"total_errors":{"type":"integer","title":"Total Errors","description":"Total failed requests since startup"},"cycle_stats":{"$ref":"#/components/schemas/ProxyCycleStats","description":"Stats for the current monitoring cycle"},"active_recordings":{"type":"integer","title":"Active Recordings","description":"Recordings currently pinned to this proxy (lives-per-IP meter)","default":0},"peak_recordings":{"type":"integer","title":"Peak Recordings","description":"Highest concurrent recordings ever seen on this proxy","default":0}},"type":"object","required":["proxy_url","ip","port","status","consecutive_failures","last_success","last_error","last_error_type","total_successes","total_errors","cycle_stats"],"title":"ProxyHealthEntry","description":"Health data for a single proxy."},"ProxyHealthResponse":{"properties":{"total_proxies":{"type":"integer","title":"Total Proxies","description":"Total configured proxies"},"healthy_count":{"type":"integer","title":"Healthy Count","description":"Number of healthy proxies"},"overall_status":{"type":"string","title":"Overall Status","description":"Overall status: all_healthy, degraded, all_unhealthy, no_proxies"},"proxies":{"items":{"$ref":"#/components/schemas/ProxyHealthEntry"},"type":"array","title":"Proxies","description":"Per-proxy health data"},"max_recordings_per_proxy":{"type":"integer","title":"Max Recordings Per Proxy","description":"Soft target for concurrent recordings per proxy (the lives-per-IP meter reference)"},"usage":{"anyOf":[{"$ref":"#/components/schemas/ProxyUsageEstimate"},{"type":"null"}],"description":"Estimated proxy bandwidth-quota usage (#214); null when unavailable"}},"type":"object","required":["total_proxies","healthy_count","overall_status","proxies","max_recordings_per_proxy"],"title":"ProxyHealthResponse","description":"Response for proxy health endpoint."},"ProxyReachabilityEntry":{"properties":{"proxy_url":{"type":"string","title":"Proxy Url"},"service_reachable":{"type":"boolean","title":"Service Reachable","description":"Whether the proxy can reach the internet (Google)"},"tiktok_reachable":{"type":"boolean","title":"Tiktok Reachable","description":"Whether the proxy can reach TikTok"},"service_latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Service Latency Ms","description":"Latency to Google in ms"},"tiktok_latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tiktok Latency Ms","description":"Latency to TikTok in ms"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if test failed"},"diagnosis":{"type":"string","title":"Diagnosis","description":"Human-readable diagnosis"}},"type":"object","required":["proxy_url","service_reachable","tiktok_reachable","service_latency_ms","tiktok_latency_ms","error","diagnosis"],"title":"ProxyReachabilityEntry","description":"Reachability test result for a single proxy."},"ProxyReachabilityResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/ProxyReachabilityEntry"},"type":"array","title":"Results"},"recommendation":{"type":"string","title":"Recommendation","description":"Overall recommendation based on test results"}},"type":"object","required":["results","recommendation"],"title":"ProxyReachabilityResponse","description":"Response for proxy reachability test."},"ProxyUsageEstimate":{"properties":{"budget_mb":{"type":"integer","title":"Budget Mb","description":"Operator-configured monthly proxy budget in MB (0 = feature dormant)"},"estimated_mb":{"type":"number","title":"Estimated Mb","description":"Estimated bandwidth used since the epoch start, in MB"},"monthly_projection_mb":{"type":"number","title":"Monthly Projection Mb","description":"Linear projection of this month's usage in MB (trend)"},"total_requests":{"type":"integer","title":"Total Requests","description":"Total live-check requests counted this epoch (successes + errors)"},"epoch_start":{"type":"string","format":"date-time","title":"Epoch Start","description":"When the in-process usage counters were last reset (restart / proxy reload)"},"warn_ratio":{"type":"number","title":"Warn Ratio","description":"Fraction of budget at which the trend WARNING fires (on the projection)"},"crit_ratio":{"type":"number","title":"Crit Ratio","description":"Fraction of budget at which the CRITICAL fires (on actual estimated usage)"},"enabled":{"type":"boolean","title":"Enabled","description":"Whether quota alerting is enabled"}},"type":"object","required":["budget_mb","estimated_mb","monthly_projection_mb","total_requests","epoch_start","warn_ratio","crit_ratio","enabled"],"title":"ProxyUsageEstimate","description":"Estimated proxy bandwidth-quota usage (#214).\n\nBytes are not directly observable (the TikTokLive lib owns its own httpx\nclient), so this is an in-process ESTIMATE derived from live-check request\ncounts. Additive/optional — the frontend can render a usage bar without any\nexisting consumer changing."},"PublicPulseResponse":{"properties":{"replays_archived":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Replays Archived"},"creators_captured":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creators Captured"},"captures_7d":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Captures 7D"},"window_days":{"type":"integer","title":"Window Days"},"step":{"type":"integer","title":"Step"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"}},"type":"object","required":["window_days","step","generated_at"],"title":"PublicPulseResponse","description":"Coarse platform totals for the public landing ledger (#334).\n\nEvery figure is floored to a step before it reaches this model, and the\nflooring happens server-side so the exact count never leaves the process.\nThat is not cosmetic: an exact figure refreshed on any cadence is a time\nseries, and at this platform's concurrency a single tick is one creator's\nsession. The step is what makes a tick unattributable.\n\nA field is ``None`` when its floored value would land in the bottom\nbucket. Below that bucket the step stops dominating the increment, and a\npublished zero is its own disclosure. Consumers must omit the cell\nentirely rather than render a zero or a dash."},"PurgePreviewItem":{"properties":{"recording_id":{"type":"string","title":"Recording Id"},"username":{"type":"string","title":"Username"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"user_id":{"type":"string","title":"User Id"},"file_size":{"type":"integer","title":"File Size"},"duration":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"},"viewer_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Viewer Count"},"retention_score":{"type":"number","title":"Retention Score"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"}},"type":"object","required":["recording_id","username","user_id","file_size","retention_score","started_at"],"title":"PurgePreviewItem","description":"A single recording that would be purged."},"PurgePreviewResponse":{"properties":{"threshold":{"type":"integer","title":"Threshold"},"is_emergency":{"type":"boolean","title":"Is Emergency"},"free_gb":{"type":"number","title":"Free Gb"},"total_gb":{"type":"number","title":"Total Gb"},"used_pct":{"type":"number","title":"Used Pct"},"purge_candidates_count":{"type":"integer","title":"Purge Candidates Count"},"total_freeable_bytes":{"type":"integer","title":"Total Freeable Bytes"},"per_user":{"items":{"$ref":"#/components/schemas/UserStorageResponse"},"type":"array","title":"Per User"},"candidates":{"items":{"$ref":"#/components/schemas/PurgePreviewItem"},"type":"array","title":"Candidates"}},"type":"object","required":["threshold","is_emergency","free_gb","total_gb","used_pct","purge_candidates_count","total_freeable_bytes","per_user","candidates"],"title":"PurgePreviewResponse","description":"Summary of what auto-purge would do right now."},"ReasonedActionRequest":{"properties":{"reason_code":{"type":"string","maxLength":40,"minLength":1,"title":"Reason Code"},"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note"}},"type":"object","required":["reason_code"],"title":"ReasonedActionRequest","description":"Body for moderation actions that require a categorical reason code."},"RecordingDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"file_path":{"type":"string","title":"File Path"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At"},"duration":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"is_age_restricted":{"type":"boolean","title":"Is Age Restricted","default":false},"is_starred":{"type":"boolean","title":"Is Starred","default":false},"visibility":{"$ref":"#/components/schemas/RecordingVisibility","default":"UNLISTED"},"status":{"type":"string","title":"Status"},"live_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Session Id"},"username":{"type":"string","title":"Username"},"user_display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"viewer_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Viewer Count"},"retention_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Retention Score"},"storage_tier":{"type":"string","title":"Storage Tier","default":"active"},"playback_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playback Mode"},"is_own":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Own"},"is_favorited":{"type":"boolean","title":"Is Favorited","default":false},"is_saved":{"type":"boolean","title":"Is Saved","default":false}},"type":"object","required":["id","user_id","file_path","started_at","status","username"],"title":"RecordingDetailResponse","description":"Detailed recording response with user info.\n\nPUBLIC DTO — served to any authenticated viewer of a PUBLIC or UNLISTED\nrecording (UNLISTED is the default visibility). Do NOT add attribution or\nPII fields here (e.g. ``*_by_user_id``, IP address, email): because the\ndefault visibility is world-readable to authenticated users, anything on\nthis model leaks \"who did what\" to every free-tier account. Attribution\nbelongs in an admin-only response gated by ``require_role(\"super_admin\")``\n(see backend #270)."},"RecordingVisibility":{"type":"string","enum":["PUBLIC","UNLISTED","PRIVATE","DMCA_REMOVED","QUARANTINED"],"title":"RecordingVisibility","description":"Visibility of a recording on the platform.\n\nPUBLIC       - visible in public library/vitrina (user chose to feature it)\nUNLISTED     - exists but hidden from public listings (default for all recordings)\nPRIVATE      - admin-only, completely hidden from any user-facing surface\nDMCA_REMOVED - hidden due to takedown request, kept 30 days then hard-deleted\nQUARANTINED  - moderation hold: hidden pending peer review + 2nd-mod deletion vote\n               (auto-reverts to UNLISTED after 48h if no second vote — see\n               ModerationActionType.QUARANTINE_EXPIRED)"},"RefreshTokenRequest":{"properties":{"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"}},"type":"object","title":"RefreshTokenRequest","description":"Optional body for /auth/refresh — mobile clients without cookies."},"RemoveProxyRequest":{"properties":{"proxy_url":{"type":"string","title":"Proxy Url","description":"SOCKS5 proxy URL to remove"}},"type":"object","required":["proxy_url"],"title":"RemoveProxyRequest","description":"Request to remove a proxy."},"RoleUpdateRequest":{"properties":{"role":{"type":"string","pattern":"^(super_admin|moderator|pro|starter|free)$","title":"Role","description":"Target role"}},"type":"object","required":["role"],"title":"RoleUpdateRequest","description":"Request to change a user's role (super_admin only)."},"SSHSetupRequest":{"properties":{"host":{"type":"string","title":"Host","description":"VPS IP address"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"Root password"},"private_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Private Key","description":"SSH private key (PEM content)"},"port":{"type":"integer","title":"Port","description":"SSH port","default":22},"username":{"type":"string","title":"Username","description":"SSH username","default":"root"}},"type":"object","required":["host"],"title":"SSHSetupRequest","description":"Request to set up a proxy via SSH on any VPS."},"SessionPoolEntry":{"properties":{"id":{"type":"string","title":"Id","description":"Unique session ID"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Human-readable label"},"session_id_masked":{"type":"string","title":"Session Id Masked","description":"Masked sessionid (first 8 chars)"},"has_ms_token":{"type":"boolean","title":"Has Ms Token","description":"Whether msToken is set"},"active_usage_count":{"type":"integer","title":"Active Usage Count","description":"Currently in use (live + recording)"},"usage_count":{"type":"integer","title":"Usage Count","description":"Total lifetime uses"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At","description":"ISO timestamp this cookie was last verified working (not a TTL)"},"added_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Added At","description":"ISO timestamp when added"},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether the session is enabled for rotation"},"consecutive_throttle":{"type":"integer","title":"Consecutive Throttle","description":"Consecutive hard throttles attributed to this cookie"},"last_throttled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Throttled At","description":"ISO timestamp of the most recent throttle, or null"},"evicted":{"type":"boolean","title":"Evicted","description":"Whether the cookie is in an eviction cooldown right now"}},"type":"object","required":["id","label","session_id_masked","has_ms_token","active_usage_count","usage_count","last_used_at","added_at","is_enabled","consecutive_throttle","last_throttled_at","evicted"],"title":"SessionPoolEntry","description":"Response model for a single session in the pool."},"SessionPoolRedundancy":{"properties":{"enabled_count":{"type":"integer","title":"Enabled Count","description":"Sessions enabled for rotation"},"usable_now":{"type":"integer","title":"Usable Now","description":"Sessions usable right now (enabled, not evicted, not throttled)"},"has_redundancy":{"type":"boolean","title":"Has Redundancy","description":"True only when more than one session is usable now"}},"type":"object","required":["enabled_count","usable_now","has_redundancy"],"title":"SessionPoolRedundancy","description":"Pool-level redundancy summary (#245)."},"SessionPoolResponse":{"properties":{"sessions":{"items":{"$ref":"#/components/schemas/SessionPoolEntry"},"type":"array","title":"Sessions","description":"All sessions in the pool"},"total":{"type":"integer","title":"Total","description":"Total session count"},"active":{"type":"integer","title":"Active","description":"Sessions currently in use"},"redundancy":{"$ref":"#/components/schemas/SessionPoolRedundancy","description":"Failover-redundancy summary for the pool"}},"type":"object","required":["sessions","total","active","redundancy"],"title":"SessionPoolResponse","description":"Response model for the full session pool status."},"SubscriptionUpdateRequest":{"properties":{"subscription_status":{"anyOf":[{"type":"string","pattern":"^(active|cancelled|past_due|expired|trialing)$"},{"type":"null"}],"title":"Subscription Status"},"subscription_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Subscription Expires At"},"storage_quota_gb":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Storage Quota Gb"}},"type":"object","title":"SubscriptionUpdateRequest","description":"Manually update a user's subscription (admin override)."},"SyncResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether sync completed without fatal errors"},"files_scanned":{"type":"integer","title":"Files Scanned","description":"Number of files found on disk"},"db_records_checked":{"type":"integer","title":"Db Records Checked","description":"Number of records checked in database"},"new_recordings_created":{"type":"integer","title":"New Recordings Created","description":"New DB entries created for discovered files"},"missing_files_marked":{"type":"integer","title":"Missing Files Marked","description":"DB entries marked as missing (file not found)"},"already_synced":{"type":"integer","title":"Already Synced","description":"Files that were already in sync"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","description":"List of error messages if any"}},"type":"object","required":["success","files_scanned","db_records_checked","new_recordings_created","missing_files_marked","already_synced","errors"],"title":"SyncResponse","description":"Response model for storage sync operation.","example":{"already_synced":148,"db_records_checked":148,"errors":[],"files_scanned":150,"missing_files_marked":0,"new_recordings_created":2,"success":true}},"SystemAlert":{"properties":{"id":{"type":"string","title":"Id"},"category":{"type":"string","enum":["critical","warning","info"],"title":"Category"},"source":{"type":"string","title":"Source"},"title":{"type":"string","title":"Title"},"message":{"type":"string","title":"Message"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["category","source","title","message"],"title":"SystemAlert","description":"Represents an operational alert in the system."},"SystemStats":{"properties":{"storage_used_bytes":{"type":"integer","title":"Storage Used Bytes","description":"Bytes used in recordings folder"},"storage_total_bytes":{"type":"integer","title":"Storage Total Bytes","description":"Total bytes available on disk"},"storage_used_gb":{"type":"number","title":"Storage Used Gb","description":"GB used in recordings folder"},"storage_total_gb":{"type":"number","title":"Storage Total Gb","description":"Total GB available on disk"},"storage_percentage":{"type":"number","title":"Storage Percentage","description":"Percentage of disk used by recordings"},"recordings_count":{"type":"integer","title":"Recordings Count","description":"Number of recording files"},"memory_used_bytes":{"type":"integer","title":"Memory Used Bytes","description":"Bytes of RAM currently used"},"memory_available_bytes":{"type":"integer","title":"Memory Available Bytes","description":"Bytes of RAM available"},"memory_total_bytes":{"type":"integer","title":"Memory Total Bytes","description":"Total bytes of RAM installed"},"memory_used_gb":{"type":"number","title":"Memory Used Gb","description":"GB of RAM currently used"},"memory_total_gb":{"type":"number","title":"Memory Total Gb","description":"Total GB of RAM installed"},"memory_percentage":{"type":"number","title":"Memory Percentage","description":"Percentage of RAM used"},"network_bytes_sent":{"type":"integer","title":"Network Bytes Sent","description":"Total bytes sent since boot"},"network_bytes_recv":{"type":"integer","title":"Network Bytes Recv","description":"Total bytes received since boot"},"network_download_rate":{"type":"number","title":"Network Download Rate","description":"Current download rate in MB/s"},"network_upload_rate":{"type":"number","title":"Network Upload Rate","description":"Current upload rate in MB/s"},"active_recordings":{"type":"integer","title":"Active Recordings","description":"Number of recordings in progress"},"total_recordings":{"type":"integer","title":"Total Recordings","description":"Total number of recordings in database"},"uptime_seconds":{"type":"integer","title":"Uptime Seconds","description":"Seconds since server started"},"uptime_formatted":{"type":"string","title":"Uptime Formatted","description":"Human-readable uptime string"},"cpu_percent":{"type":"number","title":"Cpu Percent","description":"CPU usage percentage"},"monitoring_enabled":{"type":"boolean","title":"Monitoring Enabled","description":"Whether monitoring worker is enabled"},"recording_enabled":{"type":"boolean","title":"Recording Enabled","description":"Whether recording worker is enabled"},"storage_worker_enabled":{"type":"boolean","title":"Storage Worker Enabled","description":"Whether storage auto-purge worker is enabled"},"public_login_enabled":{"type":"boolean","title":"Public Login Enabled","description":"Whether public login (email/Telegram) is enabled"},"login_notifications_enabled":{"type":"boolean","title":"Login Notifications Enabled","description":"Whether the operator is emailed on every successful login"}},"type":"object","required":["storage_used_bytes","storage_total_bytes","storage_used_gb","storage_total_gb","storage_percentage","recordings_count","memory_used_bytes","memory_available_bytes","memory_total_bytes","memory_used_gb","memory_total_gb","memory_percentage","network_bytes_sent","network_bytes_recv","network_download_rate","network_upload_rate","active_recordings","total_recordings","uptime_seconds","uptime_formatted","cpu_percent","monitoring_enabled","recording_enabled","storage_worker_enabled","public_login_enabled","login_notifications_enabled"],"title":"SystemStats","description":"System statistics response model.","example":{"active_recordings":2,"cpu_percent":23.5,"memory_available_bytes":6979321856,"memory_percentage":15.0,"memory_total_bytes":8589934592,"memory_total_gb":8.0,"memory_used_bytes":1288490188,"memory_used_gb":1.2,"monitoring_enabled":true,"network_bytes_recv":10737418240,"network_bytes_sent":1073741824,"network_download_rate":12.5,"network_upload_rate":0.3,"recording_enabled":true,"recordings_count":15,"storage_percentage":4.8,"storage_total_bytes":53687091200,"storage_total_gb":50.0,"storage_used_bytes":2576980377,"storage_used_gb":2.4,"total_recordings":47,"uptime_formatted":"4h 23m 15s","uptime_seconds":15795}},"TelegramLoginRequest":{"properties":{"id":{"type":"integer","title":"Id"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Url"},"auth_date":{"type":"integer","title":"Auth Date"},"hash":{"type":"string","title":"Hash"},"tos_accepted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Tos Accepted","description":"Affirmative ToS/Privacy acceptance captured by the consent gate in front of the Telegram widget (#299). Excluded from the Telegram HMAC check — Telegram never signed it."},"locale":{"anyOf":[{"type":"string","enum":["en","es"]},{"type":"null"}],"title":"Locale","description":"UI locale the consent text was shown in (#299). Excluded from the Telegram HMAC check — Telegram never signed it."},"referrer":{"anyOf":[{"type":"string","pattern":"^[a-zA-Z0-9-]{1,32}$"},{"type":"null"}],"title":"Referrer","description":"Partner attribution slug from the /welcome/<slug> landing cookie. Excluded from the Telegram HMAC check — Telegram never signed it."}},"type":"object","required":["id","auth_date","hash"],"title":"TelegramLoginRequest","description":"Payload from Telegram Login Widget."},"TestUserUpdateRequest":{"properties":{"is_test_user":{"type":"boolean","title":"Is Test User"}},"type":"object","required":["is_test_user"],"title":"TestUserUpdateRequest","description":"Toggle the is_test_user flag on a user."},"ToggleRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Desired state of the feature"}},"type":"object","required":["enabled"],"title":"ToggleRequest","description":"Request model for toggling a feature."},"ToggleResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the toggle was successful"},"key":{"type":"string","title":"Key","description":"Feature key that was toggled"},"enabled":{"type":"boolean","title":"Enabled","description":"Current state after toggle"},"message":{"type":"string","title":"Message","description":"Human-readable message"}},"type":"object","required":["success","key","enabled","message"],"title":"ToggleResponse","description":"Response model for toggle operations."},"UpdateSessionRequest":{"properties":{"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled","description":"Enable/disable rotation"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"New human-readable label"}},"type":"object","title":"UpdateSessionRequest","description":"Request model for patching a session (#257)."},"UserCreateRequest":{"properties":{"username":{"type":"string","pattern":"^[a-zA-Z0-9._]{2,30}$","title":"Username","description":"TikTok username (2-30 chars: letters, digits, '.', '_')"}},"type":"object","required":["username"],"title":"UserCreateRequest","description":"Request to add a user to monitoring."},"UserResponse":{"properties":{"id":{"type":"string","title":"Id"},"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"},"current_state":{"type":"string","title":"Current State"},"added_at":{"type":"string","format":"date-time","title":"Added At"},"last_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_age_restricted_live":{"type":"boolean","title":"Is Age Restricted Live","default":false},"monitoring_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Status"}},"type":"object","required":["id","username","current_state","added_at"],"title":"UserResponse","description":"User response model."},"UserStorageResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"username":{"type":"string","title":"Username"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"current_state":{"type":"string","title":"Current State"},"is_active":{"type":"boolean","title":"Is Active","default":true},"recording_count":{"type":"integer","title":"Recording Count"},"total_size":{"type":"integer","title":"Total Size"},"avg_viewers":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Viewers"},"avg_retention_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Retention Score"},"last_recording_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Recording At"}},"type":"object","required":["user_id","username","current_state","recording_count","total_size"],"title":"UserStorageResponse","description":"Per-user aggregated storage summary."},"UserWithRecordingsResponse":{"properties":{"id":{"type":"string","title":"Id"},"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"},"current_state":{"type":"string","title":"Current State"},"added_at":{"type":"string","format":"date-time","title":"Added At"},"last_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked At"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_age_restricted_live":{"type":"boolean","title":"Is Age Restricted Live","default":false},"recording_count":{"type":"integer","title":"Recording Count"},"latest_recording_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Recording Date"},"peak_viewer_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Peak Viewer Count"},"monitor_added_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitor Added At"},"can_unsubscribe":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Can Unsubscribe"},"monitoring_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Status"}},"type":"object","required":["id","username","current_state","added_at","recording_count"],"title":"UserWithRecordingsResponse","description":"User response with recording count and latest recording date."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyCodeRequest":{"properties":{"session_id":{"type":"string","maxLength":128,"minLength":1,"title":"Session Id","description":"Correlator from the magic-link request"},"code":{"type":"string","maxLength":12,"minLength":4,"title":"Code","description":"One-time numeric code from the email"}},"type":"object","required":["session_id","code"],"title":"VerifyCodeRequest","description":"Request to verify a one-time login code (OTP, #260).\n\n``session_id`` is the client-generated correlator sent with the original\n``POST /auth/magic-link`` request; the code is scoped to it. On success the\nroute returns ``AuthTokenResponse`` with access + refresh in the body\n(mobile clients have no cookies) — no polling round-trip needed."},"ViewerTimelineResponse":{"properties":{"recording_id":{"type":"string","title":"Recording Id"},"peak":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Peak","description":"Peak viewer count"},"duration":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration","description":"Recording duration in seconds"},"sample_count":{"type":"integer","title":"Sample Count","description":"Number of samples","default":0},"timeline":{"items":{"$ref":"#/components/schemas/ViewerTimelineSample"},"type":"array","title":"Timeline"}},"type":"object","required":["recording_id"],"title":"ViewerTimelineResponse","description":"Viewer count time-series for a recording."},"ViewerTimelineSample":{"properties":{"t":{"type":"integer","title":"T","description":"Seconds from recording start"},"v":{"type":"integer","title":"V","description":"Concurrent viewer count"}},"type":"object","required":["t","v"],"title":"ViewerTimelineSample","description":"Single viewer count sample at a point in time."},"VisibilityUpdateRequest":{"properties":{"visibility":{"$ref":"#/components/schemas/RecordingVisibility"}},"type":"object","required":["visibility"],"title":"VisibilityUpdateRequest","description":"Request to update the visibility of a recording."},"VisitBreakdownRow":{"properties":{"key":{"type":"string","title":"Key"},"visits":{"type":"integer","title":"Visits"},"views":{"type":"integer","title":"Views"}},"type":"object","required":["key","visits","views"],"title":"VisitBreakdownRow"},"VisitBreakdownsResponse":{"properties":{"window_days":{"type":"integer","title":"Window Days"},"countries":{"items":{"$ref":"#/components/schemas/VisitBreakdownRow"},"type":"array","title":"Countries"},"pages":{"items":{"$ref":"#/components/schemas/VisitBreakdownRow"},"type":"array","title":"Pages"},"sources":{"items":{"$ref":"#/components/schemas/VisitBreakdownRow"},"type":"array","title":"Sources"},"locales":{"items":{"$ref":"#/components/schemas/VisitBreakdownRow"},"type":"array","title":"Locales"}},"type":"object","required":["window_days","countries","pages","sources","locales"],"title":"VisitBreakdownsResponse"},"VisitDepthRow":{"properties":{"pages_in_visit":{"type":"integer","title":"Pages In Visit"},"visits":{"type":"integer","title":"Visits"},"pct":{"type":"number","title":"Pct"}},"type":"object","required":["pages_in_visit","visits","pct"],"title":"VisitDepthRow"},"VisitEventRow":{"properties":{"id":{"type":"integer","title":"Id"},"occurred_at":{"type":"string","title":"Occurred At"},"visit_id":{"type":"string","title":"Visit Id"},"stage":{"type":"string","title":"Stage"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"},"source_host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Host"},"referrer_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer Slug"},"is_bot":{"type":"boolean","title":"Is Bot"}},"type":"object","required":["id","occurred_at","visit_id","stage","country","path","locale","source_host","referrer_slug","is_bot"],"title":"VisitEventRow"},"VisitFunnelResponse":{"properties":{"window_days":{"type":"integer","title":"Window Days"},"arrived":{"type":"integer","title":"Arrived"},"drive_by":{"type":"integer","title":"Drive By"},"reached_login":{"type":"integer","title":"Reached Login"},"submitted":{"type":"integer","title":"Submitted"},"captured_attributed":{"type":"integer","title":"Captured Attributed"},"submitted_no_account":{"type":"integer","title":"Submitted No Account"},"lost":{"type":"integer","title":"Lost"},"returning_logins":{"type":"integer","title":"Returning Logins"},"captured_without_landing":{"type":"integer","title":"Captured Without Landing"},"accounts_created":{"type":"integer","title":"Accounts Created"},"leads_verified":{"type":"integer","title":"Leads Verified"},"depth":{"items":{"$ref":"#/components/schemas/VisitDepthRow"},"type":"array","title":"Depth"}},"type":"object","required":["window_days","arrived","drive_by","reached_login","submitted","captured_attributed","submitted_no_account","lost","returning_logins","captured_without_landing","accounts_created","leads_verified","depth"],"title":"VisitFunnelResponse"},"VisitSummaryResponse":{"properties":{"window_days":{"type":"integer","title":"Window Days"},"first_event_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Event At"},"days_collected":{"type":"integer","title":"Days Collected"},"oldest_event_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oldest Event Day"},"visits":{"type":"integer","title":"Visits"},"page_views":{"type":"integer","title":"Page Views"},"countries":{"type":"integer","title":"Countries"},"accounts_created":{"type":"integer","title":"Accounts Created"},"leads_verified":{"type":"integer","title":"Leads Verified"},"captured_attributed":{"type":"integer","title":"Captured Attributed"},"unattributed":{"type":"integer","title":"Unattributed"},"attribution_overhang":{"type":"integer","title":"Attribution Overhang"},"leads_lost":{"type":"integer","title":"Leads Lost"},"conversion_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion Pct"},"conversion_low_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion Low Pct"},"conversion_high_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion High Pct"},"bot_share_pct":{"type":"number","title":"Bot Share Pct"},"anomaly":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anomaly"}},"type":"object","required":["window_days","first_event_at","days_collected","oldest_event_day","visits","page_views","countries","accounts_created","leads_verified","captured_attributed","unattributed","attribution_overhang","leads_lost","conversion_pct","conversion_low_pct","conversion_high_pct","bot_share_pct","anomaly"],"title":"VisitSummaryResponse"},"VpsResizeRequest":{"properties":{"target_type":{"type":"string","pattern":"^[a-z]{2,4}[0-9]{2,3}$","title":"Target Type","description":"Target Hetzner server type (e.g. cx33)"},"confirm":{"type":"boolean","title":"Confirm","description":"Must be true to confirm resize (causes downtime)"}},"type":"object","required":["target_type","confirm"],"title":"VpsResizeRequest"}}},"tags":[{"name":"users","description":"Monitored creators and per-viewer monitors."},{"name":"recordings","description":"Replay listing, streaming, download and visibility."},{"name":"clips","description":"Clip creation and management (plan-gated)."},{"name":"health","description":"Internal health and the public sanitized status feed."},{"name":"system","description":"Real-time storage, memory, network and recording stats."},{"name":"auth","description":"Magic-link and Telegram login, token refresh."},{"name":"notifications","description":"In-app notification feed and preferences."},{"name":"billing","description":"Crypto checkout, cancel and reactivate."},{"name":"webhooks","description":"Inbound payment processor notifications."},{"name":"admin","description":"Super-admin user and subscription management."},{"name":"moderation","description":"Four-eyes deletion votes and quarantine."},{"name":"dmca","description":"DMCA takedown submission and handling."},{"name":"websocket","description":"JWT-authenticated real-time event stream."},{"name":"public","description":"Anonymous, coarse platform aggregates for the marketing surface."}]}