{"openapi":"3.1.0","info":{"title":"DiVine FunnelCake - Funnel API","description":"REST API for Nostr video analytics. Provides endpoints for querying video metadata, engagement stats, trending content, user profiles, notifications, and creator analytics.\n\n## Content Label Taxonomy\n\nEvery video response includes a `content_labels` array with label strings from trusted labelers and automated VLM classification.\n\n### Always Blocked (never returned by API)\n- `csam` - Child sexual abuse material\n- `illegal` - Other illegal content\n\n### Default-Hidden Moderation Labels\nReturned only when `content_safety=adult` or `content_safety=open` (or legacy `nsfw=show`):\n\n**Adult:** `nsfw`, `nudity`, `sexual`, `explicit`, `pornography`\n**Violence:** `violence`, `gore`, `graphic-violence`\n**Other:** `spam`, `scam`, `drugs`\n\n### Discovery Labels (informational, never filtered)\n- **Topics:** `topic:animals`, `topic:sports`, `topic:music`, etc.\n- **Source:** `archive.divine.video:vine-archive`\n- **Format:** `vertical`, `landscape`, `short-form`\n\n## Content Safety Filtering\n\nUse `content_safety` query parameter on video-returning endpoints:\n\n| Value | Effect |\n|-------|--------|\n| `default` | Hides all moderation labels (adult, violence, other). This is the default. |\n| `adult` | Shows adult content, hides gore and spam |\n| `open` | Shows all legal content (CSAM/illegal always blocked) |\n\nUse `exclude_label` for per-label exclusion (comma-separated, max 5):\n`?content_safety=adult&exclude_label=drugs,violence`\n\nLegacy `?nsfw=show` is supported as equivalent to `content_safety=open`.","license":{"name":"MIT","identifier":"MIT"},"version":"0.1.0"},"servers":[{"url":"/","description":"Current server"}],"paths":{"/api/access/{pubkey}":{"get":{"tags":["access"],"summary":"Check access to a user's content (NIP-98 authenticated).","description":"Returns whether the authenticated viewer has access to the target user's\ncontent. If the target is not private, access is always granted. If private,\naccess is granted if the viewer is the owner or an approved viewer.","operationId":"check_access","parameters":[{"name":"pubkey","in":"path","description":"Target user's Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Access check result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessCheckResponse"}}}},"401":{"description":"Authentication required or invalid"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/badges/{creator_pubkey}/{d_tag}":{"get":{"tags":["users"],"summary":"Get a badge definition.","description":"Returns the badge definition metadata for a specific badge identified by\ncreator public key and d-tag.","operationId":"get_badge_definition","parameters":[{"name":"creator_pubkey","in":"path","description":"Badge creator's public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"d_tag","in":"path","description":"Badge d-tag identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Badge definition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadgeDefinition"}}}},"404":{"description":"Badge definition not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/categories":{"get":{"tags":["categories"],"summary":"Get popular content categories.","description":"Returns categories derived from VLM topic labels with video counts.","operationId":"get_categories","parameters":[{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Number of results to skip (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"q","in":"query","description":"Filter by category name (case-insensitive substring match)","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Popular categories","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/event/{id}":{"get":{"tags":["events"],"summary":"Get any Nostr event by ID.","description":"Returns the raw Nostr event from the relay's database. This is a generic\nlookup endpoint that works for any event kind stored in the relay.","operationId":"get_event","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Event found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RawNostrEvent"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/hashtags":{"get":{"tags":["hashtags"],"summary":"Get popular hashtags.","operationId":"get_hashtags","parameters":[{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Number of results to skip (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"q","in":"query","description":"Filter by hashtag name (case-insensitive substring match, max 100 chars)","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Popular hashtags","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PopularHashtag"}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/hashtags/trending":{"get":{"tags":["hashtags"],"summary":"Get trending hashtags (time-weighted).","description":"Returns hashtags sorted by recent activity, with videos in the last\n24 hours weighted highest, followed by 7-day activity.","operationId":"get_trending_hashtags","parameters":[{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Number of results to skip (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"q","in":"query","description":"Filter by hashtag name (case-insensitive substring match, max 100 chars)","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Trending hashtags","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrendingHashtag"}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/leaderboard/creators":{"get":{"tags":["leaderboard"],"summary":"Get top creators (Viners) leaderboard by views.","description":"Returns creators ranked by total view count for the specified time period.","operationId":"get_leaderboard_creators","parameters":[{"name":"period","in":"query","description":"Time period: day, week, month (default), year, or alltime","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Creator leaderboard","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeaderboardResponse_LeaderboardCreator"}}}},"400":{"description":"Invalid period","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/leaderboard/videos":{"get":{"tags":["leaderboard"],"summary":"Get top videos leaderboard by views.","description":"Returns videos ranked by view count for the specified time period.","operationId":"get_leaderboard_videos","parameters":[{"name":"period","in":"query","description":"Time period: day, week, month (default), year, or alltime","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Video leaderboard","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeaderboardResponse_LeaderboardVideo"}}}},"400":{"description":"Invalid period","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/moderation/labels":{"get":{"tags":["moderation"],"summary":"Get content labels (admin only).","description":"Returns content labels applied by trusted labelers, optionally filtered by label value.\nRequires NIP-98 authentication with admin pubkey.","operationId":"get_content_labels","parameters":[{"name":"limit","in":"query","description":"Maximum results (default 50, max 100)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Pagination offset (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"label","in":"query","description":"Filter by specific label value","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Content labels","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentLabelsResponse"}}}},"401":{"description":"Authentication required"},"403":{"description":"Admin access required"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/moderation/reports":{"get":{"tags":["moderation"],"summary":"Get aggregated moderation reports (admin only).","description":"Returns report counts per event, sorted by report count descending.\nRequires NIP-98 authentication with admin pubkey.","operationId":"get_moderation_reports","parameters":[{"name":"limit","in":"query","description":"Maximum results (default 50, max 100)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Pagination offset (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Moderation reports","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationReportsResponse"}}}},"401":{"description":"Authentication required"},"403":{"description":"Admin access required"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/moderation/trusted-labelers":{"get":{"tags":["moderation"],"summary":"Get trusted labelers (admin only).","description":"Returns the list of trusted labeler pubkeys.\nRequires NIP-98 authentication with admin pubkey.","operationId":"get_trusted_labelers","responses":{"200":{"description":"Trusted labelers list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrustedLabelersResponse"}}}},"401":{"description":"Authentication required"},"403":{"description":"Admin access required"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["moderation"],"summary":"Add a trusted labeler (admin only).","description":"Adds a new pubkey to the trusted labelers list.\nRequires NIP-98 authentication with admin pubkey.","operationId":"add_trusted_labeler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTrustedLabelerRequest"}}},"required":true},"responses":{"200":{"description":"Trusted labeler added"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required"},"403":{"description":"Admin access required"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/moderation/trusted-labelers/{pubkey}":{"delete":{"tags":["moderation"],"summary":"Remove a trusted labeler (admin only).","description":"Removes a pubkey from the trusted labelers list.\nRequires NIP-98 authentication with admin pubkey.","operationId":"remove_trusted_labeler","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64-char hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Trusted labeler removed"},"401":{"description":"Authentication required"},"403":{"description":"Admin access required"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/search":{"get":{"tags":["search"],"summary":"Search videos by hashtag or text.","operationId":"search_videos","parameters":[{"name":"tag","in":"query","description":"Search by hashtag","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Full-text search query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"label","in":"query","description":"Filter by content moderation label (e.g., 'safe', 'nsfw', 'violence')","required":false,"schema":{"type":"string"}},{"name":"exclude_label","in":"query","description":"Exclude videos with these labels, comma-separated (max 5). Example: gore,spam","required":false,"schema":{"type":"string"}},{"name":"content_safety","in":"query","description":"Content safety preset: 'default' (hide NSFW), 'adult' (show NSFW, hide gore/spam), 'open' (no filtering)","required":false,"schema":{"type":"string"}},{"name":"nsfw","in":"query","description":"Legacy NSFW toggle: 'show' to include (default: hidden). Superseded by content_safety.","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","description":"Search type: 'video' (default) or 'sound'","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VideoStats"}}}}},"400":{"description":"Missing search parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/search/profiles":{"get":{"tags":["search"],"summary":"Search profiles by name, display_name, nip05, or about/bio.","operationId":"search_profiles","parameters":[{"name":"q","in":"query","description":"Search query (matches name, display_name, nip05, and bio/about)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Skip N results for pagination","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"sort_by","in":"query","description":"Sort order: 'followers' or 'relevance' (default)","required":false,"schema":{"type":"string"}},{"name":"has_videos","in":"query","description":"Filter to users with video content","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Profile search results","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProfileSearchResult"}}}}},"400":{"description":"Missing query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/sounds":{"get":{"tags":["sounds"],"summary":"List sounds with optional sorting.","description":"Query parameters:\n- `sort`: `trending` (default), `recent`, `popular`, or `uses`\n- `limit`: Max results (1-100, default 50)\n- `offset`: Offset for pagination (default 0)","operationId":"list_sounds","parameters":[{"name":"sort","in":"query","description":"Sort order: trending (default), recent, popular, or uses","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"List of sounds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSoundsResponseDoc"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/sounds/{id}/stats":{"get":{"tags":["sounds"],"summary":"Get stats for a specific sound.","description":"Returns detailed statistics for a single sound by event ID.","operationId":"get_sound_stats","parameters":[{"name":"id","in":"path","description":"Sound event ID (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sound stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioStats"}}}},"404":{"description":"Sound not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/stats":{"get":{"tags":["stats"],"summary":"Get overall stats.","operationId":"get_stats","responses":{"200":{"description":"Platform statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}}}}},"/api/users/bulk":{"post":{"tags":["users"],"summary":"Get multiple users in a single request.","operationId":"bulk_users","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUsersRequest"}}},"required":true},"responses":{"200":{"description":"Bulk users response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUsersResponse"}}}},"400":{"description":"Invalid request"},"500":{"description":"Internal server error"}}}},"/api/users/top":{"get":{"tags":["users"],"summary":"Get top creators ranked by total loops.","description":"Returns a leaderboard of creators sorted by their total loop count.\nUse `platform=vine` to filter to only classic Vine imports.","operationId":"get_top_creators","parameters":[{"name":"platform","in":"query","description":"Platform filter: 'vine' for Viners, 'all' for everyone","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Top creators list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TopCreator"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}":{"get":{"tags":["users"],"summary":"Get user profile and statistics.","description":"Returns combined user data including profile, social stats, content stats,\nand engagement metrics.","operationId":"get_user","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserData"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/analytics":{"get":{"tags":["users"],"summary":"Get creator analytics (NIP-98 authenticated).","description":"Returns comprehensive analytics for a creator. Requires NIP-98 authentication\nwhere the authenticated pubkey must match the requested pubkey (creators can\nonly view their own analytics).\n\nQuery parameters:\n- `period`: Time period - \"7d\", \"30d\", \"90d\", or \"all\" (default: \"30d\")","operationId":"get_creator_analytics","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"period","in":"query","description":"Time period: 7d, 30d, 90d, or all","required":false,"schema":{"type":"string"}},{"name":"window","in":"query","description":"Alias for period: 7d, 28d, 30d, 90d, or all","required":false,"schema":{"type":"string"}},{"name":"top_posts_sort","in":"query","description":"Sort top posts by views (default) or engagement","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Creator analytics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorAnalyticsResponse"}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Not authorized to view this creator's analytics"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/badges":{"get":{"tags":["users"],"summary":"Get a user's badges.","description":"Returns badges awarded to the user. By default, only returns badges the user\nhas explicitly accepted via their profile badges event (Kind 30008).","operationId":"get_user_badges","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"accepted_only","in":"query","description":"Only accepted badges (default: true)","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"User's badges","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserBadgesResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/collabs":{"get":{"tags":["users"],"summary":"Get videos where a user is tagged as a collaborator.","description":"Returns videos where the user appears in p-tags but is not the author.","operationId":"get_user_collabs","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order: recent (default), popular, likes, comments, or published","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Skip N results for pagination","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Videos where user is a collaborator","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VideoStats"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/feed":{"get":{"tags":["users"],"summary":"Get personalized video feed from followed accounts.","operationId":"get_user_feed","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key","required":true,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort: recent (default), trending, likes, comments, or published","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"before","in":"query","description":"Pagination cursor (timestamp)","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Feed response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedResponse"}}}},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/users/{pubkey}/followers":{"get":{"tags":["users"],"summary":"Get a user's followers.","description":"Returns a paginated list of pubkeys that follow the specified user.\nUse `include=profiles` to get full UserData objects instead of bare pubkeys.","operationId":"get_user_followers","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"include","in":"query","description":"Include additional data: 'profiles' returns full UserData objects","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of followers (bare pubkeys or full profiles depending on include param)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowersList"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/following":{"get":{"tags":["users"],"summary":"Get users a user is following.","description":"Returns a paginated list of pubkeys that the specified user follows.\nUse `include=profiles` to get full UserData objects instead of bare pubkeys.","operationId":"get_user_following","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"include","in":"query","description":"Include additional data: 'profiles' returns full UserData objects","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of following (bare pubkeys or full profiles depending on include param)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowingList"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/identities":{"get":{"tags":["users"],"summary":"Get a user's external identity claims.","description":"Returns the user's NIP-39 external identity claims (e.g., GitHub, Twitter).","operationId":"get_user_identities","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User's external identities","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserIdentitiesResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/notifications":{"get":{"tags":["users"],"summary":"Get notifications for a user (NIP-98 authenticated).","description":"Returns notifications when other users interact with your content\n(reactions, replies, reposts, mentions, follows, zaps).\nRequires NIP-98 authentication where the authenticated pubkey must match\nthe requested pubkey (users can only view their own notifications).","operationId":"get_user_notifications","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"types","in":"query","description":"Filter by types (comma-separated)","required":false,"schema":{"type":"string"}},{"name":"unread_only","in":"query","description":"Only show unread","required":false,"schema":{"type":"boolean"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"before","in":"query","description":"Pagination cursor (timestamp)","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Notifications list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationsResponse"}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Not authorized to view this user's notifications"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/notifications/read":{"post":{"tags":["users"],"summary":"Mark notifications as read (NIP-98 authenticated).","description":"Marks specific notifications as read by their IDs, or marks all notifications\nas read if no IDs are provided. Requires NIP-98 authentication where the\nauthenticated pubkey must match the requested pubkey (users can only modify\ntheir own notifications).","operationId":"mark_notifications_read","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkNotificationsReadRequest"}}},"required":true},"responses":{"200":{"description":"Notifications marked as read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkNotificationsReadResponse"}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Not authorized to modify this user's notifications"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/privacy":{"get":{"tags":["privacy"],"summary":"Get privacy settings for a user (NIP-98 authenticated).","description":"Returns the privacy settings for the authenticated user. Users can only\nview their own privacy settings. If no settings exist, returns defaults\n(is_private: false, empty approved_viewers).","operationId":"get_privacy_settings","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Privacy settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivacySettingsResponse"}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Not authorized to view this user's privacy settings"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"tags":["privacy"],"summary":"Set privacy settings for a user (NIP-98 authenticated).","description":"Updates the privacy settings for the authenticated user. Users can only\nmodify their own privacy settings.","operationId":"set_privacy_settings","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivacySettingsRequest"}}},"required":true},"responses":{"200":{"description":"Privacy settings updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivacySettingsResponse"}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Not authorized to modify this user's privacy settings"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["privacy"],"summary":"Delete privacy settings for a user (NIP-98 authenticated).","description":"Removes all privacy settings for the authenticated user, reverting to\na public account. Users can only delete their own privacy settings.","operationId":"delete_privacy_settings","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Privacy settings deleted"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Not authorized to delete this user's privacy settings"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/recommendations":{"get":{"tags":["users"],"summary":"Get personalized video recommendations for a user.","description":"Returns personalized recommendations from Gorse if available, with fallback\nto popular or recent videos for cold-start users or when Gorse is unavailable.\n\nQuery parameters:\n- `limit`: Max results (1-100, default 20)\n- `category`: Optional hashtag to filter by\n- `fallback`: Strategy when personalization unavailable (\"popular\" or \"recent\")","operationId":"get_user_recommendations","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 20)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"category","in":"query","description":"Filter by hashtag/category","required":false,"schema":{"type":"string"}},{"name":"fallback","in":"query","description":"Fallback: popular (default) or recent","required":false,"schema":{"type":"string"}},{"name":"content_safety","in":"query","description":"Content safety preset: 'default' (hide NSFW), 'adult' (show NSFW, hide gore/spam), 'open' (no filtering)","required":false,"schema":{"type":"string"}},{"name":"nsfw","in":"query","description":"Legacy NSFW toggle: 'show' to include (default: hidden). Superseded by content_safety.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recommended videos","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecommendationsResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/recommendations/sounds":{"get":{"tags":["sounds"],"summary":"Get personalized sound recommendations for a user.","description":"Returns personalized recommendations from Gorse if available, with fallback\nto popular or recent sounds for cold-start users or when Gorse is unavailable.","operationId":"get_user_sound_recommendations","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 20)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"fallback","in":"query","description":"Fallback: popular (default) or recent","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recommended sounds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoundRecommendationsResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/social":{"get":{"tags":["users"],"summary":"Get user's social statistics (follower/following counts only).","operationId":"get_user_social","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Social stats (returns zeros if user has no social connections)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialStats"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/sounds":{"get":{"tags":["sounds"],"summary":"Get sounds by a specific user.","description":"Returns all sounds uploaded by the given pubkey.","operationId":"get_user_sounds","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"User sounds","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AudioStats"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/users/{pubkey}/videos":{"get":{"tags":["users"],"summary":"Get videos by a specific user.","operationId":"get_user_videos","parameters":[{"name":"pubkey","in":"path","description":"Nostr public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order: recent (default), trending, popular, loops, likes, comments, or published. sort=loops returns extended format with embedded Vine metrics","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Skip N results for pagination","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"before","in":"query","description":"Only include videos created before this Unix timestamp","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"window","in":"query","description":"Optional analytics window: 7d, 28d, 30d, 90d, all","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"User's videos with additive analytics payload","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserVideoWithStats"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos":{"get":{"tags":["videos"],"summary":"List videos with optional sorting and filtering.","description":"Query parameters:\n- `sort`: `recent` (default), `trending`/`popular`, `loops`, `likes`, `comments`, `engagement`, or `published`\n- `kind`: Filter by event kind (34235 or 34236)\n- `limit`: Max results (1-100, default 50)\n- `tag`: Filter by hashtag (can combine with sort)\n- `before`: Filter videos created before this Unix timestamp\n- `after`: Filter videos created after this Unix timestamp\n- `platform`: Filter by platform (e.g., \"vine\")\n- `has_embedded_stats`: Only videos with embedded loop counts\n- `classic`: Shortcut for before + sort by loops","operationId":"list_videos","parameters":[{"name":"sort","in":"query","description":"Sort order: recent (default), trending, popular, loops, likes, comments, engagement, or published","required":false,"schema":{"type":"string"}},{"name":"kind","in":"query","description":"Filter by event kind (34235 or 34236)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"tag","in":"query","description":"Filter by hashtag","required":false,"schema":{"type":"string"}},{"name":"before","in":"query","description":"Videos created before this Unix timestamp","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"after","in":"query","description":"Videos created after this Unix timestamp","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"platform","in":"query","description":"Filter by platform (e.g., 'vine')","required":false,"schema":{"type":"string"}},{"name":"has_embedded_stats","in":"query","description":"Only videos with embedded stats","required":false,"schema":{"type":"boolean"}},{"name":"d_tag","in":"query","description":"Filter by d-tag (content hash identifier)","required":false,"schema":{"type":"string"}},{"name":"classic","in":"query","description":"Classic mode: older videos sorted by loops","required":false,"schema":{"type":"boolean"}},{"name":"nsfw","in":"query","description":"Show NSFW-labeled content: 'show' to include (default: hidden). Superseded by content_safety.","required":false,"schema":{"type":"string"}},{"name":"label","in":"query","description":"Filter by content moderation label (e.g., 'safe', 'nsfw', 'violence')","required":false,"schema":{"type":"string"}},{"name":"category","in":"query","description":"Filter by category name (convenience for label=topic:<name>)","required":false,"schema":{"type":"string"}},{"name":"exclude_label","in":"query","description":"Exclude videos with these labels, comma-separated (max 5). Example: gore,spam","required":false,"schema":{"type":"string"}},{"name":"content_safety","in":"query","description":"Content safety preset: 'default' (hide NSFW), 'adult' (show NSFW, hide gore/spam), 'open' (no filtering)","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of videos","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrendingVideo"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/bulk":{"post":{"tags":["videos"],"summary":"Get multiple videos in a single request.","operationId":"bulk_videos","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVideosRequest"}}},"required":true},"responses":{"200":{"description":"Bulk videos response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVideosResponse"}}}},"400":{"description":"Invalid request"},"500":{"description":"Internal server error"}}}},"/api/videos/events":{"get":{"tags":["videos"],"summary":"List videos with full raw Nostr events and computed stats.","description":"Returns videos with complete verifiable Nostr events alongside server-computed\nengagement statistics. This is the preferred endpoint for clients that need to\nverify event signatures.\n\nQuery parameters:\n- `sort`: `recent` (default) or `trending`\n- `kind`: Filter by event kind (34235 or 34236)\n- `limit`: Max results (1-100, default 50)\n- `before`: Filter videos created before this timestamp (for pagination)","operationId":"list_videos_with_events","parameters":[{"name":"sort","in":"query","description":"Sort order: recent, trending, or loops","required":false,"schema":{"type":"string"}},{"name":"kind","in":"query","description":"Filter by event kind (34235 or 34236)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"limit","in":"query","description":"Max results (1-100, default 50)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"before","in":"query","description":"Videos created before this Unix timestamp","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"d_tag","in":"query","description":"Filter by d-tag (content hash identifier)","required":false,"schema":{"type":"string"}},{"name":"tag","in":"query","description":"Filter by hashtag (t tag value)","required":false,"schema":{"type":"string"}},{"name":"platform","in":"query","description":"Filter by platform (e.g. vine)","required":false,"schema":{"type":"string"}},{"name":"classic","in":"query","description":"When true, default sort to loops (classic Vine-style)","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"List of videos with events and stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoEventsResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/stats/bulk":{"post":{"tags":["videos"],"summary":"Get stats for multiple videos by event ID.","description":"Returns engagement stats plus view metrics (views, loops) for up to 100 videos.\nUse this to batch stats lookups for videos received via WebSocket.","operationId":"bulk_video_stats","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVideoStatsRequest"}}},"required":true},"responses":{"200":{"description":"Stats for requested videos","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVideoStatsResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/{id}":{"get":{"tags":["videos"],"summary":"Get a single video with full event and stats.","description":"Returns the video with the complete Nostr event (verifiable signature)\nalongside computed stats (reactions, comments, reposts).\nAccepts either a Nostr event ID (64-char hex) or a d-tag (addressable\nevent identifier that stays stable across edits).","operationId":"get_video","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64-char hex) or d-tag (stable across edits)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Video with event and stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoWithEvent"}}}},"404":{"description":"Video not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/{id}/analytics":{"get":{"tags":["videos"],"summary":"Get detailed analytics for a single video post.","operationId":"get_video_analytics","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"window","in":"query","description":"Time window: 7d, 28d, 30d, 90d, or all","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Post analytics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoAnalyticsResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/{id}/comments":{"get":{"tags":["videos"],"summary":"Get comments for a video.","description":"Returns NIP-22 (Kind 1111) comment events for a video, with author\nmetadata and extracted reply thread information.\nAccepts either a Nostr event ID (64-char hex) or a d-tag.","operationId":"get_video_comments","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64-char hex) or d-tag (stable across edits)","required":true,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order: newest (default) or oldest","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max comments to return (default: 25, max: 100)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"offset","in":"query","description":"Pagination offset (default: 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Comments for the video","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentsResponse"}}}},"404":{"description":"Video not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/{id}/live-views":{"get":{"tags":["videos"],"summary":"SSE stream for live video view counts.","description":"This endpoint streams real-time view count updates every 2-3 seconds.\nThe stream includes:\n- `views`: Total view count from video_computed_loops\n- `loops`: Total computed loops\n- `viewers_now`: Current active viewers (from view_counts_live with 5-min TTL)\n\nThe endpoint is public and does not require authentication.","operationId":"live_view_stream","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"SSE stream of view updates","content":{"text/event-stream":{}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/{id}/stats":{"get":{"tags":["videos"],"summary":"Get stats for a specific video.","operationId":"get_video_stats","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Video stats found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoStats"}}}},"404":{"description":"Video not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/videos/{id}/views":{"get":{"tags":["videos"],"summary":"Get view stats for a specific video.","operationId":"get_video_views","parameters":[{"name":"id","in":"path","description":"Nostr event ID (64 character hex)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"View statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoViewStats"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"AccessCheckResponse":{"type":"object","description":"Response for access check.","required":["pubkey","has_access"],"properties":{"has_access":{"type":"boolean","description":"Whether the requesting viewer has access"},"pubkey":{"type":"string","description":"Target user pubkey"}}},"AddTrustedLabelerRequest":{"type":"object","description":"Request body for adding a trusted labeler.","required":["pubkey","reason"],"properties":{"pubkey":{"type":"string","description":"Nostr public key (64-char hex)"},"reason":{"type":"string","description":"Reason for trusting this labeler"}}},"AnalyticsQuery":{"type":"object","description":"Analytics query parameters.","properties":{"period":{"type":["string","null"],"description":"Time period: 7d, 30d, 90d, or all (default: 30d)"},"top_posts_sort":{"type":["string","null"],"description":"Top post ranking: views (default) or engagement"},"window":{"type":["string","null"],"description":"Alias for period used by newer clients (e.g. 28d)"}}},"AnalyticsSeriesPoint":{"type":"object","description":"Date/value point used in analytics time series.","required":["date","value"],"properties":{"date":{"type":"string"},"value":{"type":"integer","format":"int64","minimum":0}}},"AudioStats":{"type":"object","description":"Audio stats returned from the audio_stats view.","required":["id","pubkey","created_at","kind","title","audio_url","mime_type","sha256","duration","source","file_size","reactions","comments","reposts","usage_count","engagement_score","author_name","author_avatar"],"properties":{"audio_url":{"type":"string"},"author_avatar":{"type":"string"},"author_name":{"type":"string"},"comments":{"type":"integer","format":"int64","minimum":0},"created_at":{"type":"string","format":"date-time"},"duration":{"type":"number","format":"double"},"engagement_score":{"type":"number","format":"double"},"file_size":{"type":"integer","format":"int64","minimum":0},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"mime_type":{"type":"string"},"pubkey":{"type":"string"},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"sha256":{"type":"string"},"source":{"type":"string"},"title":{"type":"string"},"usage_count":{"type":"integer","format":"int64","minimum":0}}},"BadgeDefinition":{"type":"object","description":"Badge definition metadata (NIP-58 Kind 30009).","required":["d_tag","creator_pubkey","name","description","image","thumb","created_at","coordinate"],"properties":{"coordinate":{"type":"string","description":"NIP-33 coordinate: \"30009:<creator_pubkey>:<d_tag>\""},"created_at":{"type":"string","format":"date-time","description":"When the badge definition was created"},"creator_pubkey":{"type":"string","description":"Public key of the badge creator/issuer"},"d_tag":{"type":"string","description":"The d-tag identifier for this badge"},"description":{"type":"string","description":"Badge description (from \"description\" tag)"},"image":{"type":"string","description":"Badge image URL (from \"image\" tag)"},"name":{"type":"string","description":"Badge name (from \"name\" tag)"},"thumb":{"type":"string","description":"Badge thumbnail URL (from \"thumb\" tag)"}}},"BadgeDefinitionPath":{"type":"object","description":"Badge definition path parameters.","required":["creator_pubkey","d_tag"],"properties":{"creator_pubkey":{"type":"string","description":"Public key of the badge creator/issuer (64 character hex string)"},"d_tag":{"type":"string","description":"Badge d-tag identifier"}}},"BulkUsersRequest":{"type":"object","description":"Request body for bulk user lookup","properties":{"from_event":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FromEventRef"}]},"limit":{"type":["integer","null"],"format":"int32","description":"Maximum number of pubkeys to return (for paginating from_event results)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Offset into the resolved pubkey list (for paginating from_event results)","minimum":0},"pubkeys":{"type":"array","items":{"type":"string"}}}},"BulkUsersResponse":{"type":"object","description":"Response for bulk user lookup","required":["users","missing"],"properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Limit used for pagination (present when from_event is used with pagination)","minimum":0},"missing":{"type":"array","items":{"type":"string"}},"offset":{"type":["integer","null"],"format":"int32","description":"Offset used for pagination (present when from_event is used with pagination)","minimum":0},"source_event_id":{"type":["string","null"]},"total":{"type":["integer","null"],"format":"int64","description":"Total number of resolved pubkeys (present when from_event is used)","minimum":0},"users":{"type":"array","items":{"$ref":"#/components/schemas/UserData"}}}},"BulkVideoStatsRequest":{"type":"object","description":"Request body for bulk video stats lookup.","required":["event_ids"],"properties":{"event_ids":{"type":"array","items":{"type":"string"},"description":"List of video event IDs to get stats for (max 100)"},"window":{"type":["string","null"],"description":"Optional analytics window (e.g. 7d, 28d, 30d, 90d, all)"}}},"BulkVideoStatsResponse":{"type":"object","description":"Response for bulk video stats lookup.","required":["stats","missing"],"properties":{"missing":{"type":"array","items":{"type":"string"},"description":"Event IDs that were not found"},"stats":{"type":"object","description":"Stats keyed by event ID","additionalProperties":{"$ref":"#/components/schemas/VideoStatsOnly"},"propertyNames":{"type":"string"}}}},"BulkVideosRequest":{"type":"object","description":"Request body for bulk video lookup","properties":{"event_ids":{"type":"array","items":{"type":"string"}},"from_event":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FromEventRef"}]}}},"BulkVideosResponse":{"type":"object","description":"Response for bulk video lookup","required":["videos","missing"],"properties":{"missing":{"type":"array","items":{"type":"string"}},"source_event_id":{"type":["string","null"]},"videos":{"type":"array","items":{"$ref":"#/components/schemas/VideoStats"}}}},"CategoriesQuery":{"type":"object","description":"Categories query parameters.","properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Number of results to skip (default 0)","minimum":0},"q":{"type":["string","null"],"description":"Filter by category name (case-insensitive substring match)"}}},"Category":{"type":"object","description":"Category with video count (from label_counts table).","required":["name","video_count"],"properties":{"name":{"type":"string"},"video_count":{"type":"integer","format":"int64","minimum":0}}},"CommentEvent":{"type":"object","description":"A comment event with author metadata and extracted reply fields.","required":["id","pubkey","created_at","kind","content","sig","tags"],"properties":{"author_avatar":{"type":["string","null"],"description":"Author avatar URL"},"author_name":{"type":["string","null"],"description":"Author display name"},"content":{"type":"string","description":"Comment text content"},"created_at":{"type":"integer","format":"int64","description":"Unix timestamp of the comment"},"id":{"type":"string","description":"Comment event ID (hex)"},"kind":{"type":"integer","format":"int32","description":"Nostr event kind (1111 for NIP-22 comments)","minimum":0},"pubkey":{"type":"string","description":"Comment author public key (hex)"},"reply_to_event_id":{"type":["string","null"],"description":"Event ID of the parent comment (from lowercase \"e\" tag), null for top-level comments"},"reply_to_pubkey":{"type":["string","null"],"description":"Pubkey of the parent comment author (from lowercase \"p\" tag)"},"sig":{"type":"string","description":"Event signature (hex)"},"tags":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Full Nostr tags"}}},"CommentsQuery":{"type":"object","description":"Query parameters for the video comments endpoint.","properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Maximum number of comments to return (default: 25, max: 100)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Number of comments to skip for pagination (default: 0)","minimum":0},"sort":{"type":["string","null"],"description":"Sort order: \"newest\" (default) or \"oldest\""}}},"CommentsResponse":{"type":"object","description":"Response containing comments for a video.","required":["comments","total"],"properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/CommentEvent"},"description":"List of comments"},"total":{"type":"integer","format":"int64","description":"Total number of comments on this video","minimum":0}}},"ContentLabel":{"type":"object","description":"Content label applied by a trusted labeler.","required":["target_event_id","label_value","label_namespace","labeler_pubkey","labeled_at"],"properties":{"label_namespace":{"type":"string"},"label_value":{"type":"string"},"labeled_at":{"type":"string","format":"date-time"},"labeler_pubkey":{"type":"string"},"target_event_id":{"type":"string"}}},"ContentLabelsQuery":{"type":"object","description":"Query parameters for content labels.","properties":{"label":{"type":["string","null"],"description":"Filter by specific label value"},"limit":{"type":["integer","null"],"format":"int32","description":"Maximum number of results (default 50, max 100)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination (default 0)","minimum":0}}},"ContentLabelsResponse":{"type":"object","description":"Response for content labels listing.","required":["labels","total","offset","limit"],"properties":{"labels":{"type":"array","items":{"$ref":"#/components/schemas/ContentLabel"}},"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","minimum":0},"total":{"type":"integer","format":"int64","minimum":0}}},"CreatorAnalyticsResponse":{"type":"object","description":"Comprehensive analytics response for a creator.","required":["total_views","total_loops","total_watch_time","unique_viewers","videos","daily_stats","period","pubkey","window","summary","timeseries","top_posts"],"properties":{"daily_stats":{"type":"array","items":{"$ref":"#/components/schemas/CreatorDailyStats"},"description":"Daily stats over the period"},"period":{"type":"string","description":"Time period for the stats (7d, 30d, 90d, all)"},"pubkey":{"type":"string","description":"Creator pubkey for dashboard payload"},"summary":{"$ref":"#/components/schemas/CreatorAnalyticsSummary","description":"Dashboard summary payload"},"timeseries":{"$ref":"#/components/schemas/CreatorAnalyticsTimeseries","description":"Dashboard chart payload"},"top_posts":{"type":"array","items":{"$ref":"#/components/schemas/CreatorTopPost"},"description":"Top posts ranked by selected metric"},"total_loops":{"type":"number","format":"double","description":"Total loops (fractional) across all videos"},"total_views":{"type":"integer","format":"int64","description":"Total views across all videos","minimum":0},"total_watch_time":{"type":"integer","format":"int64","description":"Total watch time in seconds","minimum":0},"unique_viewers":{"type":"integer","format":"int64","description":"Number of unique viewers","minimum":0},"videos":{"type":"array","items":{"$ref":"#/components/schemas/CreatorVideoAnalytics"},"description":"Per-video analytics"},"window":{"type":"string","description":"Requested time window (7d, 28d, 30d, 90d, all)"}}},"CreatorAnalyticsSummary":{"type":"object","description":"Creator analytics summary for dashboard cards.","required":["video_count","reactions","comments","reposts","has_view_data"],"properties":{"avg_watch_seconds":{"type":["number","null"],"format":"double"},"comments":{"type":"integer","format":"int64","minimum":0},"completion_rate":{"type":["number","null"],"format":"double"},"engagement_rate":{"type":["number","null"],"format":"double"},"followers_gained":{"type":["integer","null"],"format":"int64","minimum":0},"has_view_data":{"type":"boolean"},"profile_visits":{"type":["integer","null"],"format":"int64","minimum":0},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"saves":{"type":["integer","null"],"format":"int64","minimum":0},"shares":{"type":["integer","null"],"format":"int64","minimum":0},"unique_viewers":{"type":["integer","null"],"format":"int64","minimum":0},"video_count":{"type":"integer","format":"int64","minimum":0},"views":{"type":["integer","null"],"format":"int64","minimum":0}}},"CreatorAnalyticsTimeseries":{"type":"object","description":"Time series data for creator analytics charts.","required":["daily_views","daily_interactions","daily_follows"],"properties":{"daily_follows":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSeriesPoint"}},"daily_interactions":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSeriesPoint"}},"daily_views":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSeriesPoint"}}}},"CreatorDailyStats":{"type":"object","description":"Daily stats for a creator.","required":["date","views","loops"],"properties":{"date":{"type":"string","description":"Date in YYYY-MM-DD format"},"loops":{"type":"number","format":"double","description":"Total loops on this date"},"views":{"type":"integer","format":"int64","description":"Total views on this date","minimum":0}}},"CreatorTopPost":{"type":"object","description":"Top post entry for creator analytics.","required":["id"],"properties":{"engagement_rate":{"type":["number","null"],"format":"double"},"id":{"type":"string"},"views":{"type":["integer","null"],"format":"int64","minimum":0}}},"CreatorVideoAnalytics":{"type":"object","description":"Analytics for a single video owned by the creator.","required":["id","views","loops","unique_viewers","avg_completion"],"properties":{"avg_completion":{"type":"number","format":"double","description":"Average completion rate (0.0 to 1.0+)"},"id":{"type":"string","description":"Video event ID"},"loops":{"type":"number","format":"double","description":"Total loops (fractional, based on watch percentage)"},"unique_viewers":{"type":"integer","format":"int64","description":"Number of unique viewers","minimum":0},"views":{"type":"integer","format":"int64","description":"Total view count","minimum":0}}},"ErrorResponse":{"type":"object","description":"Error response body.","required":["error"],"properties":{"error":{"type":"string","description":"Error message"}}},"EventPath":{"type":"object","description":"Event path parameters.","required":["id"],"properties":{"id":{"type":"string","description":"Nostr event ID (64 character hex string)"}}},"ExternalIdentity":{"type":"object","description":"An external identity claim (NIP-39 Kind 10011).","required":["platform","username","proof"],"properties":{"platform":{"type":"string","description":"Platform name (e.g., \"github\", \"twitter\", \"mastodon\")"},"proof":{"type":"string","description":"Proof URL or identifier"},"username":{"type":"string","description":"Username on that platform"}}},"FeedQuery":{"type":"object","description":"Feed query parameters","properties":{"before":{"type":["integer","null"],"format":"int64","description":"Cursor for pagination (videos created before this timestamp)"},"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","description":"Skip N results for offset-based pagination","minimum":0},"sort":{"type":"string"}}},"FeedResponse":{"type":"object","description":"Feed response with pagination","required":["videos","has_more"],"properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"videos":{"type":"array","items":{"$ref":"#/components/schemas/VideoStats"}}}},"FollowersList":{"type":"object","description":"Paginated list of followers.","required":["followers","total","offset","limit"],"properties":{"followers":{"type":"array","items":{"type":"string"}},"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","minimum":0},"total":{"type":"integer","format":"int64","minimum":0}}},"FollowersQuery":{"type":"object","description":"Followers/following query parameters.","properties":{"include":{"type":["string","null"],"description":"Include additional data. Use \"profiles\" to get full UserData objects instead of bare pubkeys."},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination","minimum":0}}},"FollowersWithProfilesResponse":{"type":"object","description":"Followers list with full profile data.","required":["followers","total","offset","limit"],"properties":{"followers":{"type":"array","items":{"$ref":"#/components/schemas/UserData"}},"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","minimum":0},"total":{"type":"integer","format":"int64","minimum":0}}},"FollowingList":{"type":"object","description":"Paginated list of following.","required":["following","total","offset","limit"],"properties":{"following":{"type":"array","items":{"type":"string"}},"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","minimum":0},"total":{"type":"integer","format":"int64","minimum":0}}},"FollowingWithProfilesResponse":{"type":"object","description":"Following list with full profile data.","required":["following","total","offset","limit"],"properties":{"following":{"type":"array","items":{"$ref":"#/components/schemas/UserData"}},"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","minimum":0},"total":{"type":"integer","format":"int64","minimum":0}}},"FromEventRef":{"type":"object","description":"Reference to a Nostr event for resolving lists","required":["kind","pubkey"],"properties":{"d_tag":{"type":["string","null"]},"kind":{"type":"integer","format":"int32","minimum":0},"pubkey":{"type":"string"}}},"HashtagsQuery":{"type":"object","description":"Hashtags query parameters.","properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Number of results to skip (default 0)","minimum":0},"q":{"type":["string","null"],"description":"Filter by hashtag name (case-insensitive substring match, max 100 chars)"}}},"LeaderboardCreator":{"type":"object","description":"Creator entry in the leaderboard.","required":["pubkey","name","display_name","picture","views","unique_viewers","loops","videos_with_views"],"properties":{"display_name":{"type":"string","description":"Display name (empty string if not set)"},"loops":{"type":"number","format":"double","description":"Total loops in this period (Float64 from ClickHouse aggregation)"},"name":{"type":"string","description":"Profile name (empty string if not set)"},"picture":{"type":"string","description":"Profile picture URL (empty string if not set)"},"pubkey":{"type":"string","description":"Creator's public key"},"unique_viewers":{"type":"integer","format":"int64","description":"Unique viewers in this period","minimum":0},"videos_with_views":{"type":"integer","format":"int64","description":"Number of videos with views in this period","minimum":0},"views":{"type":"integer","format":"int64","description":"Total views in this period","minimum":0}}},"LeaderboardPeriod":{"type":"string","description":"Time period for leaderboard queries.","enum":["Day","Week","Month","Year","AllTime"]},"LeaderboardResponse_LeaderboardCreator":{"type":"object","description":"Response for leaderboard queries.","required":["period","entries"],"properties":{"entries":{"type":"array","items":{"type":"object","description":"Creator entry in the leaderboard.","required":["pubkey","name","display_name","picture","views","unique_viewers","loops","videos_with_views"],"properties":{"display_name":{"type":"string","description":"Display name (empty string if not set)"},"loops":{"type":"number","format":"double","description":"Total loops in this period (Float64 from ClickHouse aggregation)"},"name":{"type":"string","description":"Profile name (empty string if not set)"},"picture":{"type":"string","description":"Profile picture URL (empty string if not set)"},"pubkey":{"type":"string","description":"Creator's public key"},"unique_viewers":{"type":"integer","format":"int64","description":"Unique viewers in this period","minimum":0},"videos_with_views":{"type":"integer","format":"int64","description":"Number of videos with views in this period","minimum":0},"views":{"type":"integer","format":"int64","description":"Total views in this period","minimum":0}}},"description":"Leaderboard entries"},"period":{"type":"string","description":"The time period for this leaderboard"}}},"LeaderboardResponse_LeaderboardVideo":{"type":"object","description":"Response for leaderboard queries.","required":["period","entries"],"properties":{"entries":{"type":"array","items":{"type":"object","description":"Video entry in the leaderboard.","required":["id","pubkey","title","thumbnail","d_tag","video_url","kind","author_name","author_avatar","views","unique_viewers","loops"],"properties":{"author_avatar":{"type":"string","description":"Creator's avatar URL"},"author_name":{"type":"string","description":"Creator's display name"},"d_tag":{"type":"string","description":"d-tag identifier"},"id":{"type":"string","description":"Video event ID"},"kind":{"type":"integer","format":"int32","description":"Event kind (34235 or 34236)","minimum":0},"loops":{"type":"number","format":"double","description":"Total loops in this period (Float64 from ClickHouse aggregation)"},"pubkey":{"type":"string","description":"Creator's public key"},"thumbnail":{"type":"string","description":"Thumbnail URL"},"title":{"type":"string","description":"Video title"},"unique_viewers":{"type":"integer","format":"int64","description":"Unique viewers in this period","minimum":0},"video_url":{"type":"string","description":"Video URL"},"views":{"type":"integer","format":"int64","description":"Total views in this period","minimum":0}}},"description":"Leaderboard entries"},"period":{"type":"string","description":"The time period for this leaderboard"}}},"LeaderboardVideo":{"type":"object","description":"Video entry in the leaderboard.","required":["id","pubkey","title","thumbnail","d_tag","video_url","kind","author_name","author_avatar","views","unique_viewers","loops"],"properties":{"author_avatar":{"type":"string","description":"Creator's avatar URL"},"author_name":{"type":"string","description":"Creator's display name"},"d_tag":{"type":"string","description":"d-tag identifier"},"id":{"type":"string","description":"Video event ID"},"kind":{"type":"integer","format":"int32","description":"Event kind (34235 or 34236)","minimum":0},"loops":{"type":"number","format":"double","description":"Total loops in this period (Float64 from ClickHouse aggregation)"},"pubkey":{"type":"string","description":"Creator's public key"},"thumbnail":{"type":"string","description":"Thumbnail URL"},"title":{"type":"string","description":"Video title"},"unique_viewers":{"type":"integer","format":"int64","description":"Unique viewers in this period","minimum":0},"video_url":{"type":"string","description":"Video URL"},"views":{"type":"integer","format":"int64","description":"Total views in this period","minimum":0}}},"ListSoundsQuery":{"type":"object","description":"Query parameters for listing sounds.","properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination (default 0)","minimum":0},"sort":{"type":["string","null"],"description":"Sort order: trending (default), recent, popular, or uses"}}},"ListSoundsResponseDoc":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/AudioStats"}},{"type":"array","items":{"$ref":"#/components/schemas/TrendingAudio"}}]},"ListVideosQuery":{"type":"object","description":"List videos query parameters.","properties":{"after":{"type":["integer","null"],"format":"int64","description":"Filter videos created after this Unix timestamp"},"before":{"type":["integer","null"],"format":"int64","description":"Filter videos created before this Unix timestamp"},"category":{"type":["string","null"],"description":"Filter by category name (convenience for label=topic:<name>)"},"classic":{"type":["boolean","null"],"description":"Classic mode: combines before date + sort by loops (shortcut)"},"content_safety":{"type":["string","null"],"description":"Content safety preset: \"default\" (hide NSFW), \"adult\" (show NSFW, hide gore/spam), \"open\" (no filtering)"},"d_tag":{"type":["string","null"],"description":"Filter by d-tag (addressable event identifier / content hash)"},"exclude_label":{"type":"array","items":{"type":"string"},"description":"Per-label exclusion list, comma-separated (max 5). Example: exclude_label=gore,spam"},"has_embedded_stats":{"type":["boolean","null"],"description":"Only include videos with embedded stats (imported with loop counts)"},"kind":{"type":["integer","null"],"format":"int32","description":"Filter by event kind (34235 or 34236)","minimum":0},"label":{"type":["string","null"],"description":"Filter by content moderation label (e.g., \"safe\", \"nsfw\", \"violence\")"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"nsfw":{"type":["string","null"],"description":"Show NSFW-labeled content: 'show' to include (default: hidden). Superseded by content_safety."},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination (default 0)","minimum":0},"platform":{"type":["string","null"],"description":"Filter by platform (e.g., \"vine\" for imported classic vines)"},"sort":{"type":["string","null"],"description":"Sort order: recent (default), trending, popular, or loops"},"tag":{"type":["string","null"],"description":"Filter by hashtag (combines with sort)"}}},"LiveViewStats":{"type":"object","description":"Live view statistics for SSE streaming (real-time display).\n\nUsed by the `/api/videos/{id}/live-views` SSE endpoint to stream\nreal-time view counts to clients.","required":["views","loops","viewers_now"],"properties":{"loops":{"type":"number","format":"double","description":"Total loops (computed from watch time) across the logical video identity"},"viewers_now":{"type":"integer","format":"int32","description":"Current viewers from view_counts_live table (5-minute TTL)","minimum":0},"views":{"type":"integer","format":"int64","description":"Total views from the user-facing logical video aggregate","minimum":0}}},"MarkNotificationsReadRequest":{"type":"object","description":"Request body for marking notifications as read","properties":{"notification_ids":{"type":"array","items":{"type":"string"},"description":"List of notification IDs (source_event_id) to mark as read.\nIf empty or not provided, marks all notifications as read."}}},"MarkNotificationsReadResponse":{"type":"object","description":"Response for marking notifications as read","required":["marked_count","marked_all"],"properties":{"marked_all":{"type":"boolean","description":"Whether all notifications were marked as read"},"marked_count":{"type":"integer","format":"int64","description":"Number of notifications marked as read","minimum":0}}},"ModerationReport":{"type":"object","description":"Aggregated moderation report for a target event.","required":["target_event_id","report_count","unique_reporters","latest_report_at","report_types"],"properties":{"latest_report_at":{"type":"string","format":"date-time"},"report_count":{"type":"integer","format":"int64","minimum":0},"report_types":{"type":"array","items":{"type":"string"}},"target_event_id":{"type":"string"},"unique_reporters":{"type":"integer","format":"int64","minimum":0}}},"ModerationReportsQuery":{"type":"object","description":"Query parameters for moderation reports.","properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Maximum number of results (default 50, max 100)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination (default 0)","minimum":0}}},"ModerationReportsResponse":{"type":"object","description":"Response for moderation reports listing.","required":["reports","total","offset","limit"],"properties":{"limit":{"type":"integer","format":"int32","minimum":0},"offset":{"type":"integer","format":"int32","minimum":0},"reports":{"type":"array","items":{"$ref":"#/components/schemas/ModerationReport"}},"total":{"type":"integer","format":"int64","minimum":0}}},"Notification":{"type":"object","description":"A notification for a user","required":["source_pubkey","source_event_id","source_kind","source_created_at","referenced_event_id","notification_type","created_at","read"],"properties":{"created_at":{"type":"integer","format":"int32","description":"When the notification was created (Unix timestamp)","minimum":0},"notification_type":{"type":"string","description":"Type of notification (reaction, reply, repost, mention, follow, zap)"},"read":{"type":"integer","format":"int32","description":"Has this notification been read? (0 = unread, 1 = read)","minimum":0},"referenced_event_id":{"type":"string","description":"The event ID that was referenced (if any)"},"source_created_at":{"type":"integer","format":"int32","description":"When the source event was created (Unix timestamp)","minimum":0},"source_event_id":{"type":"string","description":"The event ID that triggered the notification"},"source_kind":{"type":"integer","format":"int32","description":"The kind of the source event","minimum":0},"source_pubkey":{"type":"string","description":"The pubkey of the user who triggered the notification"}}},"NotificationsQueryParams":{"type":"object","description":"Notifications query parameters.","properties":{"before":{"type":["integer","null"],"format":"int64","description":"Pagination cursor (timestamp)"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"types":{"type":["string","null"],"description":"Filter by notification types (comma-separated: reaction,reply,repost,mention,follow,zap)"},"unread_only":{"type":"boolean","description":"Only show unread notifications"}}},"NotificationsResponse":{"type":"object","description":"Response for notifications endpoint","required":["notifications","unread_count","has_more"],"properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"unread_count":{"type":"integer","format":"int64","minimum":0}}},"PopularHashtag":{"type":"object","description":"Popular hashtag with video counts.","required":["hashtag","video_count","unique_creators","total_loops","last_used","thumbnail"],"properties":{"hashtag":{"type":"string"},"last_used":{"type":"string","format":"date-time"},"thumbnail":{"type":"string"},"total_loops":{"type":"integer","format":"int64","minimum":0},"unique_creators":{"type":"integer","format":"int64","minimum":0},"video_count":{"type":"integer","format":"int64","minimum":0}}},"PrivacySettingsRequest":{"type":"object","description":"Request body for setting privacy settings.","required":["is_private"],"properties":{"approved_viewers":{"type":"array","items":{"type":"string"},"description":"List of approved viewer pubkeys (64-char hex)"},"is_divine_only":{"type":"boolean","description":"Whether content is divine-only (served via REST API only, hidden from Nostr relay)"},"is_private":{"type":"boolean","description":"Whether the account is private"}}},"PrivacySettingsResponse":{"type":"object","description":"Response for privacy settings.","required":["pubkey","is_private","is_divine_only","approved_viewers"],"properties":{"approved_viewers":{"type":"array","items":{"type":"string"},"description":"List of approved viewer pubkeys"},"is_divine_only":{"type":"boolean","description":"Whether content is divine-only (served via REST API only, hidden from Nostr relay)"},"is_private":{"type":"boolean","description":"Whether the account is private"},"pubkey":{"type":"string","description":"User pubkey"}}},"ProfileSearchQuery":{"type":"object","description":"Profile search query parameters.","required":["q"],"properties":{"has_videos":{"type":["boolean","null"],"description":"Filter to users with video content"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Skip N results for pagination","minimum":0},"q":{"type":"string","description":"Search query for profile name, display_name, or nip05"},"sort_by":{"type":["string","null"],"description":"Sort order: \"followers\" or \"relevance\" (default)"}}},"ProfileSearchResult":{"type":"object","description":"Profile search result for creator discovery.\nContains minimal profile data needed for search results.","required":["pubkey","name","display_name","nip05","about","picture","banner","follower_count","video_count","nip05_verified"],"properties":{"about":{"type":"string"},"banner":{"type":"string"},"display_name":{"type":"string"},"follower_count":{"type":"integer","format":"int64","minimum":0},"name":{"type":"string"},"nip05":{"type":"string"},"nip05_verified":{"type":"integer","format":"int32","minimum":0},"picture":{"type":"string"},"pubkey":{"type":"string"},"video_count":{"type":"integer","format":"int64","minimum":0}}},"RawNostrEvent":{"type":"object","description":"A raw Nostr event for API responses.\nThis structure exactly matches the Nostr event format so clients can verify signatures.","required":["id","pubkey","created_at","kind","tags","content","sig"],"properties":{"content":{"type":"string"},"created_at":{"type":"integer","format":"int64"},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"pubkey":{"type":"string"},"sig":{"type":"string"},"tags":{"type":"array","items":{"type":"array","items":{"type":"string"}}}}},"RecommendationsQuery":{"type":"object","description":"Query parameters for recommendations endpoint.","properties":{"category":{"type":["string","null"],"description":"Filter by hashtag/category"},"content_safety":{"type":["string","null"],"description":"Content safety preset: \"default\" (hide NSFW), \"adult\" (show NSFW, hide gore/spam), \"open\" (no filtering)"},"fallback":{"type":["string","null"],"description":"Fallback strategy: \"popular\" (default) or \"recent\""},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 20)","minimum":0},"nsfw":{"type":["string","null"],"description":"Legacy NSFW toggle: 'show' to include NSFW content (default: hidden). Superseded by content_safety."}}},"RecommendationsResponse":{"type":"object","description":"Recommendations response.","required":["videos","source"],"properties":{"source":{"type":"string","description":"Source of recommendations"},"videos":{"type":"array","items":{"$ref":"#/components/schemas/VideoStats"},"description":"Recommended videos with stats"}}},"RetentionPoint":{"type":"object","description":"Retention point for a post analytics response.","required":["second","viewer_pct"],"properties":{"second":{"type":"integer","format":"int32","minimum":0},"viewer_pct":{"type":"number","format":"double"}}},"SearchQuery":{"type":"object","description":"Search query parameters.","properties":{"content_safety":{"type":["string","null"],"description":"Content safety preset: \"default\" (hide NSFW), \"adult\" (show NSFW, hide gore/spam), \"open\" (no filtering)"},"exclude_label":{"type":"array","items":{"type":"string"},"description":"Per-label exclusion list, comma-separated (max 5). Example: exclude_label=gore,spam"},"label":{"type":["string","null"],"description":"Filter by content moderation label (e.g., \"safe\", \"nsfw\", \"violence\")"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"nsfw":{"type":["string","null"],"description":"Legacy NSFW toggle: 'show' to include NSFW content (default: hidden). Superseded by content_safety."},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination (default 0)","minimum":0},"q":{"type":["string","null"],"description":"Full-text search query"},"tag":{"type":["string","null"],"description":"Search by hashtag"},"type":{"type":["string","null"],"description":"Optional type filter: \"video\" (default) or \"sound\""}}},"SearchResultsDoc":{"oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/VideoStats"}},{"type":"array","items":{"$ref":"#/components/schemas/AudioStats"}}]},"SocialStats":{"type":"object","description":"Social stats for a user (follower and following counts).","required":["follower_count","following_count"],"properties":{"follower_count":{"type":"integer","format":"int64","minimum":0},"following_count":{"type":"integer","format":"int64","minimum":0}}},"SoundRecommendationsResponse":{"type":"object","description":"Response for sound recommendations.","required":["sounds","source"],"properties":{"sounds":{"type":"array","items":{"$ref":"#/components/schemas/AudioStats"},"description":"Recommended sounds with stats"},"source":{"type":"string","description":"Source of recommendations: \"personalized\", \"popular\", or \"recent\""}}},"Stats":{"type":"object","description":"Stats response.","required":["total_events","total_videos","vine_videos"],"properties":{"total_events":{"type":"integer","format":"int64","description":"Total number of Nostr events stored","minimum":0},"total_videos":{"type":"integer","format":"int64","description":"Total number of video events","minimum":0},"vine_videos":{"type":"integer","format":"int64","description":"Number of classic Vine archive videos","minimum":0}}},"TopCreator":{"type":"object","description":"Top creator entry with aggregated stats.\n\nUsed for leaderboard-style queries (top Viners, top creators by engagement, etc.)","required":["pubkey","total_loops","video_count","platform"],"properties":{"name":{"type":["string","null"],"description":"Display name from profile"},"picture":{"type":["string","null"],"description":"Avatar URL from profile"},"platform":{"type":"string","description":"Platform filter used (e.g., \"vine\", \"all\")"},"pubkey":{"type":"string","description":"Creator's public key (64-char hex)"},"total_loops":{"type":"integer","format":"int64","description":"Total loop count across all videos","minimum":0},"video_count":{"type":"integer","format":"int64","description":"Number of videos","minimum":0}}},"TrendingAudio":{"type":"object","description":"Trending audio with score.","required":["id","pubkey","created_at","kind","title","audio_url","mime_type","sha256","duration","source","file_size","reactions","comments","reposts","usage_count","engagement_score","author_name","author_avatar","trending_score"],"properties":{"audio_url":{"type":"string"},"author_avatar":{"type":"string"},"author_name":{"type":"string"},"comments":{"type":"integer","format":"int64","minimum":0},"created_at":{"type":"string","format":"date-time"},"duration":{"type":"number","format":"double"},"engagement_score":{"type":"number","format":"double"},"file_size":{"type":"integer","format":"int64","minimum":0},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"mime_type":{"type":"string"},"pubkey":{"type":"string"},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"sha256":{"type":"string"},"source":{"type":"string"},"title":{"type":"string"},"trending_score":{"type":"number","format":"double"},"usage_count":{"type":"integer","format":"int64","minimum":0}}},"TrendingHashtag":{"type":"object","description":"Trending hashtag with time-weighted scoring.","required":["hashtag","video_count","videos_24h","videos_7d","unique_creators","last_used","trending_score","thumbnail"],"properties":{"hashtag":{"type":"string"},"last_used":{"type":"string","format":"date-time"},"thumbnail":{"type":"string"},"trending_score":{"type":"integer","format":"int64","minimum":0},"unique_creators":{"type":"integer","format":"int64","minimum":0},"video_count":{"type":"integer","format":"int64","minimum":0},"videos_24h":{"type":"integer","format":"int64","minimum":0},"videos_7d":{"type":"integer","format":"int64","minimum":0}}},"TrendingVideo":{"type":"object","description":"Trending video with score.","required":["id","pubkey","created_at","kind","d_tag","title","content","thumbnail","video_url","reactions","comments","reposts","engagement_score","loops","views","author_name","author_avatar","published_at","language","trending_score","text_track_ref","text_track_content"],"properties":{"author_avatar":{"type":"string","description":"Author avatar URL from Kind 0 profile"},"author_name":{"type":"string","description":"Author display name (from embedded data or Kind 0 profile)"},"comments":{"type":"integer","format":"int64","minimum":0},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"engagement_score":{"type":"integer","format":"int64","minimum":0},"expiration_at":{"type":["string","null"],"description":"NIP-40 expiration timestamp (None = never expires)"},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"language":{"type":"string","description":"ISO-639-1 language code from NIP-32 self-labeling (e.g. \"en\", \"es\")"},"loops":{"type":"integer","format":"int64","description":"Safe loop count (prefers computed, falls back to capped embedded for pre-2025)","minimum":0},"pubkey":{"type":"string"},"published_at":{"type":"integer","format":"int32","description":"Publication timestamp (from NIP-71 published_at tag, fallback to created_at)","minimum":0},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"text_track_content":{"type":"string","description":"Full VTT subtitle content (empty string if no subtitles)"},"text_track_ref":{"type":"string","description":"NIP-33 addressable coordinate for the subtitle event"},"thumbnail":{"type":"string"},"title":{"type":"string"},"trending_score":{"type":"number","format":"double","description":"Trending score (engagement weighted by recency) — from the trending_videos view.\nMust appear before subtitle fields to match `SELECT tv.*, sub_cols` column order."},"video_url":{"type":"string"},"views":{"type":"integer","format":"int64","description":"Total user-facing view count from the logical video aggregate","minimum":0}}},"TrustedLabeler":{"type":"object","description":"A trusted labeler entry.","required":["pubkey","added_at","reason"],"properties":{"added_at":{"type":"string","format":"date-time"},"pubkey":{"type":"string"},"reason":{"type":"string"}}},"TrustedLabelerPath":{"type":"object","description":"Path parameter for trusted labeler deletion.","required":["pubkey"],"properties":{"pubkey":{"type":"string","description":"Nostr public key (64-char hex)"}}},"TrustedLabelersResponse":{"type":"object","description":"Response for trusted labelers listing.","required":["labelers"],"properties":{"labelers":{"type":"array","items":{"$ref":"#/components/schemas/TrustedLabeler"}}}},"UserBadge":{"type":"object","description":"A badge awarded to a user, combining award event info with the badge definition.","required":["definition","award_event_id","awarded_at","awarded_by"],"properties":{"award_event_id":{"type":"string","description":"Event ID of the Kind 8 award event"},"awarded_at":{"type":"string","format":"date-time","description":"When the badge was awarded"},"awarded_by":{"type":"string","description":"Public key of the awarder (may differ from badge creator)"},"definition":{"$ref":"#/components/schemas/BadgeDefinition","description":"The badge definition"}}},"UserBadgesQuery":{"type":"object","description":"Query parameters for user badges endpoint.","properties":{"accepted_only":{"type":["boolean","null"],"description":"Only return badges the user has explicitly accepted (default: true)"}}},"UserBadgesResponse":{"type":"object","description":"Response containing a user's badges.","required":["badges"],"properties":{"badges":{"type":"array","items":{"$ref":"#/components/schemas/UserBadge"},"description":"List of badges the user has received"}}},"UserData":{"type":"object","description":"Combined user data for the API response.","required":["pubkey","social","stats","engagement"],"properties":{"badges":{"type":["array","null"],"items":{"$ref":"#/components/schemas/UserBadge"},"description":"Optional embedded badge awards. Use `GET /api/users/{pubkey}/badges` for the canonical list."},"engagement":{"$ref":"#/components/schemas/UserEngagementResponse","description":"Engagement received"},"identities":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ExternalIdentity"},"description":"Optional embedded identity claims. Use `GET /api/users/{pubkey}/identities` for the canonical list."},"profile":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/UserProfileResponse","description":"Profile information"}]},"pubkey":{"type":"string","description":"User's public key (hex string)"},"social":{"$ref":"#/components/schemas/SocialStats","description":"Social statistics (followers/following)"},"stats":{"$ref":"#/components/schemas/UserStatsResponse","description":"Content statistics"}}},"UserEngagementResponse":{"type":"object","description":"Engagement response without the pubkey (nested in UserData).","required":["total_reactions","total_comments","total_reposts","unique_reactors","unique_commenters","unique_reposters","total_loops","total_views"],"properties":{"total_comments":{"type":"integer","format":"int64","minimum":0},"total_loops":{"type":"number","format":"double","description":"Total loops computed from watch time (seconds_watched / video_duration)"},"total_reactions":{"type":"integer","format":"int64","minimum":0},"total_reposts":{"type":"integer","format":"int64","minimum":0},"total_views":{"type":"integer","format":"int64","description":"Total view count across all videos","minimum":0},"unique_commenters":{"type":"integer","format":"int64","minimum":0},"unique_reactors":{"type":"integer","format":"int64","minimum":0},"unique_reposters":{"type":"integer","format":"int64","minimum":0}}},"UserIdentitiesResponse":{"type":"object","description":"Response containing a user's external identity claims.","required":["identities"],"properties":{"identities":{"type":"array","items":{"$ref":"#/components/schemas/ExternalIdentity"},"description":"List of external identity claims"}}},"UserPath":{"type":"object","description":"User path parameters.","required":["pubkey"],"properties":{"pubkey":{"type":"string","description":"Nostr public key (64 character hex string)"}}},"UserProfileResponse":{"type":"object","description":"Profile response without the pubkey (nested in UserData).","required":["profile_updated","name","display_name","about","picture","banner","nip05","lud16","website","nip05_verified"],"properties":{"about":{"type":"string"},"banner":{"type":"string"},"display_name":{"type":"string"},"lud16":{"type":"string"},"name":{"type":"string"},"nip05":{"type":"string"},"nip05_verified":{"type":"boolean"},"picture":{"type":"string"},"profile_updated":{"type":"string","format":"date-time"},"website":{"type":"string"}}},"UserSoundsQuery":{"type":"object","description":"Query parameters for user sounds.","properties":{"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Offset for pagination (default 0)","minimum":0}}},"UserStatsResponse":{"type":"object","description":"Stats response without the pubkey (nested in UserData).","required":["video_count","horizontal_videos","vertical_videos","note_count","reaction_count","comment_count","repost_count","total_events"],"properties":{"comment_count":{"type":"integer","format":"int64","minimum":0},"first_activity":{"type":["string","null"],"format":"date-time"},"horizontal_videos":{"type":"integer","format":"int64","minimum":0},"last_activity":{"type":["string","null"],"format":"date-time"},"note_count":{"type":"integer","format":"int64","minimum":0},"reaction_count":{"type":"integer","format":"int64","minimum":0},"repost_count":{"type":"integer","format":"int64","minimum":0},"total_events":{"type":"integer","format":"int64","minimum":0},"vertical_videos":{"type":"integer","format":"int64","minimum":0},"video_count":{"type":"integer","format":"int64","minimum":0}}},"UserVideoWithStats":{"allOf":[{"$ref":"#/components/schemas/VideoStats"},{"type":"object","required":["stats"],"properties":{"stats":{"$ref":"#/components/schemas/VideoStatsOnly"}}}],"description":"User video response with additive analytics payload."},"UserVideosPath":{"type":"object","description":"User videos path parameters.","required":["pubkey"],"properties":{"pubkey":{"type":"string","description":"Nostr public key (64 character hex string)"}}},"UserVideosQuery":{"type":"object","description":"User videos query parameters.","properties":{"before":{"type":["integer","null"],"format":"int64","description":"Return only videos created before this Unix timestamp"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"offset":{"type":["integer","null"],"format":"int32","description":"Skip N results for pagination","minimum":0},"sort":{"type":["string","null"],"description":"Sort order: recent (default), popular, likes, comments, or published"},"window":{"type":["string","null"],"description":"Optional analytics window (7d, 28d, 30d, 90d, all)"}}},"VideoAnalyticsQuery":{"type":"object","description":"Query parameters for post analytics endpoint.","properties":{"window":{"type":["string","null"],"description":"Time window: 7d, 28d, 30d, 90d, or all (default: 30d)"}}},"VideoAnalyticsResponse":{"type":"object","description":"Analytics response for a single video post detail screen.","required":["id","window","summary","traffic_sources","retention_curve"],"properties":{"id":{"type":"string"},"retention_curve":{"type":"array","items":{"$ref":"#/components/schemas/RetentionPoint"}},"summary":{"$ref":"#/components/schemas/VideoAnalyticsSummary"},"traffic_sources":{"$ref":"#/components/schemas/VideoTrafficSources"},"window":{"type":"string"}}},"VideoAnalyticsSummary":{"type":"object","description":"Summary payload for a single post analytics response.","required":["reactions","comments","reposts","has_view_data"],"properties":{"avg_watch_seconds":{"type":["number","null"],"format":"double"},"comments":{"type":"integer","format":"int64","minimum":0},"completion_rate":{"type":["number","null"],"format":"double"},"engagement_rate":{"type":["number","null"],"format":"double"},"followers_gained":{"type":["integer","null"],"format":"int64","minimum":0},"has_view_data":{"type":"boolean"},"profile_visits":{"type":["integer","null"],"format":"int64","minimum":0},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"rewatch_rate":{"type":["number","null"],"format":"double"},"saves":{"type":["integer","null"],"format":"int64","minimum":0},"shares":{"type":["integer","null"],"format":"int64","minimum":0},"total_watch_seconds":{"type":["number","null"],"format":"double"},"unique_viewers":{"type":["integer","null"],"format":"int64","minimum":0},"views":{"type":["integer","null"],"format":"int64","minimum":0}}},"VideoEventStats":{"type":"object","description":"Computed stats for a video (separate from the raw event).","required":["reactions","comments","reposts","engagement_score","trending_score","author_name","author_avatar"],"properties":{"author_avatar":{"type":"string","description":"Author avatar URL from Kind 0 profile"},"author_name":{"type":"string","description":"Author display name (from embedded data or Kind 0 profile)"},"comments":{"type":"integer","format":"int64","description":"Number of comments/replies","minimum":0},"computed_loops":{"type":["number","null"],"format":"double","description":"Computed loop count from view tracking"},"content_labels":{"type":"array","items":{"type":"string"},"description":"Content moderation labels (e.g. [\"nsfw\", \"violence\"]) — enriched post-query"},"embedded_loops":{"type":["integer","null"],"format":"int64","description":"Embedded loop count from video metadata (if available)","minimum":0},"engagement_score":{"type":"integer","format":"int64","description":"Engagement score (weighted sum)","minimum":0},"reactions":{"type":"integer","format":"int64","description":"Number of reactions (kind 7)","minimum":0},"reposts":{"type":"integer","format":"int64","description":"Number of reposts (kind 6/16)","minimum":0},"text_track_content":{"type":"string","description":"Subtitle WebVTT content from Kind 39307 event"},"text_track_ref":{"type":"string","description":"Subtitle text-track URL reference from video event's text-track tag"},"trending_score":{"type":"number","format":"double","description":"Trending score (time-weighted engagement)"}}},"VideoEventsQuery":{"type":"object","description":"Query parameters for video events endpoint.","properties":{"before":{"type":["integer","null"],"format":"int64","description":"Filter videos created before this Unix timestamp (for pagination)"},"classic":{"type":["boolean","null"],"description":"When true, use \"loops\" sort (classic Vine-style)"},"d_tag":{"type":["string","null"],"description":"Filter by d-tag (addressable event identifier / content hash)"},"kind":{"type":["integer","null"],"format":"int32","description":"Filter by event kind (34235 or 34236)","minimum":0},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50)","minimum":0},"platform":{"type":["string","null"],"description":"Filter by platform (e.g. \"vine\")"},"sort":{"type":["string","null"],"description":"Sort order: recent (default), trending, or loops"},"tag":{"type":["string","null"],"description":"Filter by hashtag (t tag value)"}}},"VideoEventsResponse":{"type":"object","description":"Response for paginated video feed with raw events.","required":["videos","has_more"],"properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"videos":{"type":"array","items":{"$ref":"#/components/schemas/VideoWithEvent"}}}},"VideoHashtag":{"type":"object","description":"Video hashtag mapping.","required":["event_id","hashtag","created_at","pubkey","kind","title","thumbnail","d_tag","author_name","author_avatar"],"properties":{"author_avatar":{"type":"string","description":"Author avatar URL from Kind 0 profile"},"author_name":{"type":"string","description":"Author display name (from embedded data or Kind 0 profile)"},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"event_id":{"type":"string"},"hashtag":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"pubkey":{"type":"string"},"thumbnail":{"type":"string"},"title":{"type":"string"}}},"VideoHashtagWithEngagement":{"type":"object","description":"Video with hashtag and engagement for combined queries.","required":["id","pubkey","created_at","kind","hashtag","title","thumbnail","d_tag","video_url","loops","embedded_likes","engagement_score","author_name","author_avatar","reactions","comments","reposts"],"properties":{"author_avatar":{"type":"string","description":"Author avatar URL from Kind 0 profile"},"author_name":{"type":"string","description":"Author display name (from embedded data or Kind 0 profile)"},"comments":{"type":"integer","format":"int64","minimum":0},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"embedded_likes":{"type":"integer","format":"int64","minimum":0},"engagement_score":{"type":"integer","format":"int64","minimum":0},"hashtag":{"type":"string"},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"loops":{"type":"integer","format":"int64","minimum":0},"pubkey":{"type":"string"},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"thumbnail":{"type":"string"},"title":{"type":"string"},"video_url":{"type":"string"}}},"VideoStats":{"type":"object","description":"Video stats returned from the video_stats view.","required":["id","pubkey","created_at","kind","d_tag","title","content","thumbnail","video_url","reactions","comments","reposts","engagement_score","loops","views","author_name","author_avatar","published_at","language","text_track_ref","text_track_content"],"properties":{"author_avatar":{"type":"string","description":"Author avatar URL from Kind 0 profile"},"author_name":{"type":"string","description":"Author display name (from embedded data or Kind 0 profile)"},"comments":{"type":"integer","format":"int64","minimum":0},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"engagement_score":{"type":"integer","format":"int64","minimum":0},"expiration_at":{"type":["string","null"],"description":"NIP-40 expiration timestamp (None = never expires)"},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"language":{"type":"string","description":"ISO-639-1 language code from NIP-32 self-labeling (e.g. \"en\", \"es\")"},"loops":{"type":"integer","format":"int64","description":"Safe loop count (prefers computed, falls back to capped embedded for pre-2025)","minimum":0},"pubkey":{"type":"string"},"published_at":{"type":"integer","format":"int32","description":"Publication timestamp (from NIP-71 published_at tag, fallback to created_at)","minimum":0},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"text_track_content":{"type":"string","description":"Full VTT subtitle content (empty string if no subtitles)"},"text_track_ref":{"type":"string","description":"NIP-33 addressable coordinate for the subtitle event (e.g. \"39307:pubkey:subtitles:d-tag\")"},"thumbnail":{"type":"string"},"title":{"type":"string"},"video_url":{"type":"string"},"views":{"type":"integer","format":"int64","description":"Total user-facing view count from the logical video aggregate","minimum":0}}},"VideoStatsOnly":{"type":"object","description":"Stats for a single video (used in bulk response).","required":["reactions","comments","reposts","engagement_score","has_view_data"],"properties":{"avg_watch_seconds":{"type":["number","null"],"format":"double","description":"Average watch time in seconds, null when unknown/unavailable."},"comments":{"type":"integer","format":"int64","description":"Number of comments/replies","minimum":0},"completion_rate":{"type":["number","null"],"format":"double","description":"Completion rate in [0, 1], null when unknown/unavailable."},"embedded_loops":{"type":["integer","null"],"format":"int64","description":"Embedded loop count from video metadata (if available)","minimum":0},"engagement_score":{"type":"number","format":"double","description":"Engagement score (weighted sum)"},"has_view_data":{"type":"boolean","description":"True when view data is known for this event."},"loops":{"type":["number","null"],"format":"double","description":"Total computed loops from view tracking."},"reactions":{"type":"integer","format":"int64","description":"Number of reactions (kind 7)","minimum":0},"reposts":{"type":"integer","format":"int64","description":"Number of reposts (kind 6/16)","minimum":0},"rewatch_rate":{"type":["number","null"],"format":"double","description":"Rewatch rate in [0, 1], null when unknown/unavailable."},"saves":{"type":["integer","null"],"format":"int64","description":"Saves are not currently tracked in ClickHouse aggregates.","minimum":0},"shares":{"type":["integer","null"],"format":"int64","description":"Shares are not currently tracked in ClickHouse aggregates.","minimum":0},"text_track_content":{"type":["string","null"],"description":"Raw text track (subtitle) content, if available"},"text_track_ref":{"type":["string","null"],"description":"Reference to text track (subtitle) event, if available"},"total_watch_seconds":{"type":["number","null"],"format":"double","description":"Total watch time in seconds, null when unknown/unavailable."},"unique_viewers":{"type":["integer","null"],"format":"int64","description":"Number of unique viewers, null when unknown/unavailable.","minimum":0},"views":{"type":["integer","null"],"format":"int64","description":"Total view count from view tracking, null when unknown/unavailable.","minimum":0}}},"VideoStatsPath":{"type":"object","description":"Video stats path parameters.","required":["id"],"properties":{"id":{"type":"string","description":"Nostr event ID (64-char hex) or d-tag (addressable event identifier)"}}},"VideoTrafficSources":{"type":"object","description":"Traffic source breakdown for a post analytics response.","properties":{"external":{"type":["number","null"],"format":"double"},"following":{"type":["number","null"],"format":"double"},"for_you":{"type":["number","null"],"format":"double"},"hashtag":{"type":["number","null"],"format":"double"},"profile":{"type":["number","null"],"format":"double"},"search":{"type":["number","null"],"format":"double"},"shares":{"type":["number","null"],"format":"double"}}},"VideoViewStats":{"type":"object","description":"View statistics for a video (public, safe to expose via API).\n\nFor replaceable videos, totals follow the stable logical video identity.","required":["views","unique_viewers","total_watch_time","avg_completion"],"properties":{"avg_completion":{"type":"number","format":"double"},"total_watch_time":{"type":"integer","format":"int64","minimum":0},"unique_viewers":{"type":"integer","format":"int64","minimum":0},"views":{"type":"integer","format":"int64","minimum":0}}},"VideoWithEvent":{"type":"object","description":"Video response combining raw Nostr event with computed stats.\nThis is the preferred response format for video API endpoints.","required":["event","stats"],"properties":{"event":{"$ref":"#/components/schemas/RawNostrEvent","description":"The raw Nostr event (verifiable with signature)"},"stats":{"$ref":"#/components/schemas/VideoEventStats","description":"Computed stats from the relay"}}},"VideoWithLoops":{"type":"object","description":"Video with embedded loop counts (from Vine archive).","required":["id","pubkey","created_at","kind","d_tag","title","thumbnail","video_url","sha256","duration","mime_type","size","dimensions","blurhash","loops","embedded_likes","embedded_comments","embedded_reposts","platform","author_name","author_avatar","published_at","reactions","comments","reposts","engagement_score","views","language","text_track_ref","text_track_content"],"properties":{"author_avatar":{"type":"string","description":"Author avatar URL from Kind 0 profile"},"author_name":{"type":"string"},"blurhash":{"type":"string","description":"Blurhash placeholder"},"comments":{"type":"integer","format":"int64","minimum":0},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"dimensions":{"type":"string","description":"Dimensions (e.g., \"1920x1080\")"},"duration":{"type":"integer","format":"int32","description":"Duration in seconds","minimum":0},"embedded_comments":{"type":"integer","format":"int64","minimum":0},"embedded_likes":{"type":"integer","format":"int64","minimum":0},"embedded_reposts":{"type":"integer","format":"int64","minimum":0},"engagement_score":{"type":"integer","format":"int64","minimum":0},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"language":{"type":"string","description":"ISO-639-1 language code from NIP-32 self-labeling (e.g. \"en\", \"es\")"},"loops":{"type":"integer","format":"int64","minimum":0},"mime_type":{"type":"string","description":"MIME type (e.g., \"video/mp4\")"},"platform":{"type":"string"},"pubkey":{"type":"string"},"published_at":{"type":"integer","format":"int32","description":"Publication timestamp (from NIP-71 published_at tag, fallback to created_at)","minimum":0},"reactions":{"type":"integer","format":"int64","minimum":0},"reposts":{"type":"integer","format":"int64","minimum":0},"sha256":{"type":"string","description":"SHA256 hash of the video file (for media.divine.video/{sha256}.mp4)"},"size":{"type":"integer","format":"int64","description":"File size in bytes","minimum":0},"text_track_content":{"type":"string","description":"Full VTT subtitle content (empty string if no subtitles)"},"text_track_ref":{"type":"string","description":"NIP-33 addressable coordinate for the subtitle event"},"thumbnail":{"type":"string"},"title":{"type":"string"},"video_url":{"type":"string"},"views":{"type":"integer","format":"int64","description":"Total user-facing view count from the logical video aggregate","minimum":0}}}}},"tags":[{"name":"videos","description":"Video metadata, statistics, and bulk operations"},{"name":"events","description":"Generic Nostr event lookups"},{"name":"users","description":"User profiles, videos, social graphs, feeds, notifications, and analytics"},{"name":"search","description":"Video and profile search"},{"name":"hashtags","description":"Hashtag discovery and trending"},{"name":"stats","description":"Platform statistics"},{"name":"privacy","description":"Account privacy management"},{"name":"access","description":"Access control checks"},{"name":"moderation","description":"Content moderation and trusted labeler management"},{"name":"categories","description":"Content category discovery"},{"name":"badges","description":"NIP-58 badge definitions and awards"},{"name":"sounds","description":"Audio/sound metadata, statistics, and recommendations"},{"name":"feeds","description":"RSS 2.0 feeds with podcast namespace extensions (served at /feed/*)"}]}