{"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/calendar/collections/{coordinate}":{"get":{"tags":["calendar"],"summary":"Get a single calendar collection by NIP-52 coordinate.","operationId":"get_calendar_collection","parameters":[{"name":"coordinate","in":"path","description":"NIP-52 calendar collection coordinate in kind:pubkey:d-tag form","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Calendar collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarCollection"}}}},"404":{"description":"Calendar collection not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/calendar/events":{"get":{"tags":["calendar"],"summary":"List calendar events with simple REST filters.","operationId":"list_calendar_events","parameters":[{"name":"host_pubkey","in":"query","description":"Restrict to events by this host pubkey","required":false,"schema":{"type":"string"}},{"name":"topic","in":"query","description":"Restrict to events tagged with this topic","required":false,"schema":{"type":"string"}},{"name":"near","in":"query","description":"Restrict to events whose geohashes start with this prefix","required":false,"schema":{"type":"string"}},{"name":"starts_after","in":"query","description":"Inclusive lower bound for event start time (Unix seconds)","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"starts_before","in":"query","description":"Inclusive upper bound for event start time (Unix seconds)","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"attending","in":"query","description":"Restrict to events with an accepted RSVP from this pubkey","required":false,"schema":{"type":"string"}},{"name":"include_past","in":"query","description":"Include events whose start time is already in the past","required":false,"schema":{"type":"boolean"}},{"name":"order","in":"query","description":"Sort order: start_asc, start_desc, or created_desc","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":"Number of results to skip (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Calendar event list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CalendarEvent"}}}}},"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/calendar/events/{coordinate}":{"get":{"tags":["calendar"],"summary":"Get a single calendar event by NIP-52 coordinate.","operationId":"get_calendar_event","parameters":[{"name":"coordinate","in":"path","description":"NIP-52 calendar coordinate in kind:pubkey:d-tag form","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Calendar event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarEvent"}}}},"404":{"description":"Calendar 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/calendar/events/{coordinate}/parent-calendars":{"get":{"tags":["calendar"],"summary":"List parent calendar collections that reference an event coordinate.","operationId":"get_calendar_event_parent_calendars","parameters":[{"name":"coordinate","in":"path","description":"NIP-52 calendar event coordinate in kind:pubkey:d-tag form","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Parent calendar collections","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CalendarCollection"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/calendar/events/{coordinate}/rsvp-counts":{"get":{"tags":["calendar"],"summary":"Get precomputed RSVP counts for a calendar event coordinate.","operationId":"get_calendar_event_rsvp_counts","parameters":[{"name":"coordinate","in":"path","description":"NIP-52 calendar event coordinate in kind:pubkey:d-tag form","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"RSVP counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RsvpCounts"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/calendar/events/{coordinate}/rsvps":{"get":{"tags":["calendar"],"summary":"List RSVP events for a calendar event coordinate.","operationId":"get_calendar_event_rsvps","parameters":[{"name":"coordinate","in":"path","description":"NIP-52 calendar event coordinate in kind:pubkey:d-tag form","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":"Number of results to skip (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"RSVP list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Rsvp"}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/calendar/events/{coordinate}/videos":{"get":{"tags":["calendar"],"summary":"List public videos referencing a calendar event coordinate.","operationId":"get_calendar_event_videos","parameters":[{"name":"coordinate","in":"path","description":"NIP-52 calendar event coordinate in kind:pubkey:d-tag form","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":"Number of results to skip (default 0)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Videos referencing the calendar event","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/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/events":{"post":{"tags":["events"],"operationId":"publish_event_doc","parameters":[{"name":"authorization","in":"header","description":"NIP-98 authorization header: `Nostr <base64-encoded event>`","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Signed Nostr event to publish. This path is publicly exposed on the shared relay/API host but handled by the relay service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventRequestDoc"}}},"required":true},"responses":{"200":{"description":"Event accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}},"401":{"description":"Missing or invalid NIP-98 authorization","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}},"403":{"description":"NIP-98 signer does not match event author","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}},"422":{"description":"Event rejected by relay validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}},"500":{"description":"Internal relay processing error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}},"503":{"description":"Publish endpoint disabled because relay URL is not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishEventResponseDoc"}}}}}}},"/api/experimental/users/{pubkey}/cold-start-recommendations":{"get":{"tags":["experimental"],"operationId":"get_experimental_cold_start_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":"preset","in":"query","description":"Experimental blend preset","required":false,"schema":{"$ref":"#/components/schemas/ExperimentalBlendPreset"}},{"name":"debug","in":"query","description":"Include evaluation summary metadata","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Experimental cold-start recommendations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentalColdStartResponse"}}}},"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/lists/{pubkey}/{d_tag}/stats":{"get":{"tags":["lists"],"summary":"Get stats for a people list.","description":"Returns aggregated stats (member count, total videos, total loops) for a\nNIP-51 kind 30000 categorized people list.","operationId":"get_list_stats","parameters":[{"name":"pubkey","in":"path","description":"List owner's public key (64 character hex)","required":true,"schema":{"type":"string"}},{"name":"d_tag","in":"path","description":"List d-tag identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"People list stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeopleListStats"}}}},"404":{"description":"People list not found","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 (legacy v1 path).","description":"Returns a bare JSON array of search results for backwards compatibility\nwith pre-PR-#238 clients. New integrations should use [`search_videos_v2`]\nat `/api/v2/search`.","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 25)","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":"label","in":"query","description":"Filter by content moderation label","required":false,"schema":{"type":"string"}},{"name":"exclude_label","in":"query","description":"Exclude videos with these labels, comma-separated (max 5)","required":false,"schema":{"type":"string"}},{"name":"content_safety","in":"query","description":"Content safety preset: 'default', 'adult', or 'open'","required":false,"schema":{"type":"string"}},{"name":"nsfw","in":"query","description":"Legacy NSFW toggle: 'show' to include","required":false,"schema":{"type":"string"}},{"name":"moderation_profile","in":"query","description":"Moderation profile: default, strict, or user","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","description":"Search type: 'video' (default) or 'sound'","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bare JSON array of search results (pre-PR-#238 shape)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchResultItemDoc"}}}}},"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 (legacy v1 path).","description":"Returns a bare JSON array of profile search results for backwards\ncompatibility with pre-PR-#238 clients. New integrations should use\n[`search_profiles_v2`] at `/api/v2/search/profiles`.","operationId":"search_profiles","parameters":[{"name":"q","in":"query","description":"Search query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results (1-100, default 25)","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', 'videos', 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":"Bare JSON array of profile search results (pre-PR-#238 shape)","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 a confirmed collaborator.","description":"Returns videos where the user accepted collaborator credit and the latest\ncreator video version still names them in p-tags.","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 (legacy v1 path).","description":"Returns the pre-PR-#238 wrapper shape: `{ followers, total, offset, limit }`\n(or `{ followers: [profiles], ... }` when `include=profiles` is set).\nNew integrations should use [`get_user_followers_v2`] at\n`/api/v2/users/{pubkey}/followers`.","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 25)","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":"include","in":"query","description":"Include additional data: 'profiles' returns full UserData objects","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Followers with `{ followers, total, offset, limit }` wrapper","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 (legacy v1 path).","description":"Returns the pre-PR-#238 wrapper shape: `{ following, total, offset, limit }`\n(or `{ following: [profiles], ... }` when `include=profiles` is set).\nNew integrations should use [`get_user_following_v2`] at\n`/api/v2/users/{pubkey}/following`.","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 25)","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":"include","in":"query","description":"Include additional data: 'profiles' returns full UserData objects","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Following with `{ following, total, offset, limit }` wrapper","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- `cursor`: Opaque cursor for infinite scroll (preferred)\n- `offset`: Legacy offset pagination alias\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":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","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 (legacy v1 path).","description":"Returns a bare JSON array of videos for backwards compatibility with\npre-PR-#238 clients. New integrations should use [`get_user_videos_v2`]\nat `/api/v2/users/{pubkey}/videos`.","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 25)","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":"Filter 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":"Bare JSON array of user's videos (pre-PR-#238 shape)","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/v2/search":{"get":{"tags":["v2"],"summary":"Search videos by hashtag or text (v2 envelope shape).","description":"Returns a `{ data, pagination }` envelope. The legacy `/api/search`\npath returns a bare JSON array — see [`search_videos`].","operationId":"search_videos_v2","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 25)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","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":"moderation_profile","in":"query","description":"Moderation profile: default, strict, or user","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 with pagination envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedSearchResultsResponseDoc"}}}},"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/v2/search/profiles":{"get":{"tags":["v2"],"summary":"Search profiles by name, display_name, nip05, or about/bio (v2 envelope shape).","description":"Returns a `{ data, pagination }` envelope. The legacy\n`/api/search/profiles` path returns a bare JSON array — see\n[`search_profiles`].","operationId":"search_profiles_v2","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 25)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"sort_by","in":"query","description":"Sort order: 'followers', 'videos', 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 with pagination envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedProfileSearchResponseDoc"}}}},"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/v2/users/{pubkey}/followers":{"get":{"tags":["v2"],"summary":"Get a user's followers (v2 envelope shape).","description":"Returns a `{ data, pagination }` envelope. The legacy\n`/api/users/{pubkey}/followers` path returns a `{ followers, total, offset, limit }`\nobject — see [`get_user_followers`].","operationId":"get_user_followers_v2","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 25)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","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":"Followers with pagination envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUserListResponseDoc"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/users/{pubkey}/following":{"get":{"tags":["v2"],"summary":"Get users a user is following (v2 envelope shape).","description":"Returns a `{ data, pagination }` envelope. The legacy\n`/api/users/{pubkey}/following` path returns a `{ following, total, offset, limit }`\nobject — see [`get_user_following`].","operationId":"get_user_following_v2","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 25)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","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":"Following with pagination envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUserListResponseDoc"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/users/{pubkey}/videos":{"get":{"tags":["v2"],"summary":"Get videos by a specific user (v2 envelope shape).","description":"Returns a `{ data, pagination }` envelope. The legacy\n`/api/users/{pubkey}/videos` path returns a bare JSON array — see\n[`get_user_videos`].","operationId":"get_user_videos_v2","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 25)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"before","in":"query","description":"Legacy timestamp pagination alias","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 (envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUserVideosResponseDoc"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/videos":{"get":{"tags":["v2"],"summary":"List videos with optional sorting and filtering (v2 envelope shape).","description":"Returns a `{ data, pagination }` envelope. The legacy `/api/videos` path\ncontinues to return a bare JSON array of videos for backwards\ncompatibility — see [`list_videos`].\n\nQuery parameters:\n- `sort`: `recent` (default), `trending`/`popular`, `loops`, `likes`, `comments`, `engagement`, `published`, or `watching` (24h view count, no age decay — surfaces classic Vines that are getting current attention)\n- `kind`: Filter by event kind (34235 or 34236)\n- `limit`: Max results (1-100, default 25)\n- `cursor` / `offset` / `before`: pagination\n- `tag`: Filter by hashtag (can combine with sort)\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_v2","parameters":[{"name":"sort","in":"query","description":"Sort order: recent (default), trending, popular, loops, likes, comments, engagement, published, or watching (24-hour CDN view count with no age decay)","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 25)","required":false,"schema":{"type":"integer","format":"int32","minimum":0}},{"name":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","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":"Legacy timestamp pagination alias","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"}},{"name":"moderation_profile","in":"query","description":"Moderation profile: default, strict, or user","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of videos with pagination envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedVideosResponseDoc"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v2/videos/{id}/comments":{"get":{"tags":["v2"],"summary":"Get comments for a video (v2 envelope shape).","description":"Returns NIP-22 (Kind 1111) comment events with a `{ data, pagination }`\nenvelope. The legacy `/api/videos/{id}/comments` path returns\n`{ comments, total }` — see [`get_video_comments`].","operationId":"get_video_comments_v2","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":"cursor","in":"query","description":"Opaque pagination cursor","required":false,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Legacy offset pagination alias","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Comments with pagination envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedCommentsResponseDoc"}}}},"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":{"get":{"tags":["videos"],"summary":"List videos (legacy v1 path).","description":"Returns a bare JSON array of videos for backwards compatibility with\npre-PR-#238 clients. New integrations should use [`list_videos_v2`] at\n`/api/v2/videos`, which returns a `{ data, pagination }` envelope.","operationId":"list_videos","parameters":[{"name":"sort","in":"query","description":"Sort order: recent (default), trending, popular, loops, likes, comments, engagement, published, or watching (24-hour CDN view count with no age decay)","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 25)","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":"tag","in":"query","description":"Filter by hashtag","required":false,"schema":{"type":"string"}},{"name":"before","in":"query","description":"Filter 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"}},{"name":"moderation_profile","in":"query","description":"Moderation profile: default, strict, or user","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bare JSON array of videos (pre-PR-#238 shape)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VideoListItemDoc"}}}}},"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 (legacy v1 path).","description":"Returns the pre-PR-#238 wrapper shape: `{ comments, total }`.\nNew integrations should use [`get_video_comments_v2`] at\n`/api/v2/videos/{id}/comments`.","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":"Skip N comments for pagination","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"Comments wrapped in `{ comments, total }`","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"}}}},"CalendarCollection":{"type":"object","description":"Latest materialized NIP-52 calendar collection row.","required":["id","pubkey","created_at","kind","content","coordinate","d_tag","title","event_coordinates"],"properties":{"content":{"type":"string"},"coordinate":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"event_coordinates":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"pubkey":{"type":"string"},"title":{"type":"string"}}},"CalendarCoordinatePath":{"type":"object","description":"Calendar coordinate path parameter.","required":["coordinate"],"properties":{"coordinate":{"type":"string","description":"NIP-52 coordinate in `kind:pubkey:d-tag` form."}}},"CalendarEvent":{"type":"object","description":"Latest materialized NIP-52 calendar event row.","required":["id","pubkey","created_at","kind","content","coordinate","d_tag","title","summary","image","start_at","start_tzid","end_tzid","locations","geohashes","topics","participant_pubkeys"],"properties":{"content":{"type":"string"},"coordinate":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"end_at":{"type":["integer","null"],"format":"int64"},"end_tzid":{"type":"string"},"geohashes":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"image":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"locations":{"type":"array","items":{"type":"string"}},"participant_pubkeys":{"type":"array","items":{"type":"string"}},"pubkey":{"type":"string"},"start_at":{"type":"integer","format":"int64"},"start_tzid":{"type":"string"},"summary":{"type":"string"},"title":{"type":"string"},"topics":{"type":"array","items":{"type":"string"}}}},"CalendarEventsQuery":{"type":"object","description":"Calendar discovery query parameters.","properties":{"attending":{"type":["string","null"],"description":"Restrict discovery to events with an accepted RSVP by this pubkey."},"host_pubkey":{"type":["string","null"],"description":"Restrict discovery to a host pubkey."},"include_past":{"type":["boolean","null"],"description":"Include past events when true."},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 50).","minimum":0},"near":{"type":["string","null"],"description":"Restrict discovery to geohashes beginning with this prefix."},"offset":{"type":["integer","null"],"format":"int32","description":"Number of results to skip (default 0).","minimum":0},"order":{"type":["string","null"],"description":"Sort order: `start_asc`, `start_desc`, or `created_desc`."},"starts_after":{"type":["integer","null"],"format":"int64","description":"Inclusive lower bound for `start_at`."},"starts_before":{"type":["integer","null"],"format":"int64","description":"Inclusive upper bound for `start_at`."},"topic":{"type":["string","null"],"description":"Restrict discovery to a topic tag."}}},"CalendarPageQuery":{"type":"object","description":"Shared query parameters for list-style event subresources.","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}}},"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":{"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"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)"}}},"ExperimentalBlendPreset":{"type":"string","enum":["balanced","classic_heavy","fresh_heavy"]},"ExperimentalColdStartQuery":{"type":"object","properties":{"debug":{"type":["boolean","null"],"description":"Include debug summary fields for evaluation tooling"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 20)","minimum":0},"preset":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ExperimentalBlendPreset","description":"Experimental blend preset"}]}}},"ExperimentalColdStartResponse":{"type":"object","required":["videos","source","preset"],"properties":{"debug":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ExperimentalDebugSummary","description":"Optional evaluation summary for scripts and manual comparisons"}]},"preset":{"$ref":"#/components/schemas/ExperimentalBlendPreset","description":"Blend preset used for this response"},"source":{"type":"string","description":"Response source marker"},"videos":{"type":"array","items":{"$ref":"#/components/schemas/ExperimentalRecommendationItem"},"description":"Experimental recommendation items with research metadata"}}},"ExperimentalDebugSummary":{"type":"object","required":["unique_creators","classic_count","fresh_count","general_count","classic_share","fresh_share","general_share","duplicate_ids","generator_mix"],"properties":{"classic_count":{"type":"integer","description":"Number of items from the classic cohort","minimum":0},"classic_share":{"type":"number","format":"double","description":"Share of results from the classic cohort"},"duplicate_ids":{"type":"integer","description":"Number of overlapping candidate IDs observed before final deduplication","minimum":0},"fresh_count":{"type":"integer","description":"Number of items from the fresh cohort","minimum":0},"fresh_share":{"type":"number","format":"double","description":"Share of results from the fresh cohort"},"general_count":{"type":"integer","description":"Number of items from the general discovery cohort","minimum":0},"general_share":{"type":"number","format":"double","description":"Share of results from the general discovery cohort"},"generator_mix":{"type":"array","items":{"$ref":"#/components/schemas/GeneratorMixEntry"},"description":"Generator breakdown for the inspected list"},"unique_creators":{"type":"integer","description":"Count of distinct creator pubkeys in the result set","minimum":0}}},"ExperimentalRecommendationItem":{"type":"object","required":["video","generator","cohort"],"properties":{"cohort":{"$ref":"#/components/schemas/RecommendationCohort","description":"Cohort classification used by the experimental pipeline"},"generator":{"type":"string","description":"Candidate generator that produced this item"},"video":{"$ref":"#/components/schemas/VideoStats","description":"Underlying video payload"}}},"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":{"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"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 25)","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"}}},"GeneratorMixEntry":{"type":"object","required":["generator","count"],"properties":{"count":{"type":"integer","description":"Number of items produced by that generator in the final list","minimum":0},"generator":{"type":"string","description":"Generator name"}}},"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"}}]},"ListStatsPath":{"type":"object","description":"Path parameters for people list stats.","required":["pubkey","d_tag"],"properties":{"d_tag":{"type":"string","description":"List d-tag identifier"},"pubkey":{"type":"string","description":"List owner's public key (64 character hex string)"}}},"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)"},"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"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 25)","minimum":0},"moderation_profile":{"type":["string","null"],"description":"Moderation profile: default, strict, or user."},"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, loops, likes, comments,\nengagement, published, or watching (24h view count, no age decay)."},"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 returned by the notifications list endpoint.\nFor grouped follow notifications, one representative ID may mark multiple\nunderlying Kind 3 republish events 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 (API response)","required":["source_pubkey","source_event_id","source_kind","source_created_at","referenced_event_id","notification_type","created_at","read"],"properties":{"content":{"type":["string","null"],"description":"Content of the source event (reaction emoji or comment body)"},"created_at":{"type":"integer","format":"int32","description":"When the notification was created (Unix timestamp)","minimum":0},"notification_type":{"type":"string","description":"Type of notification. Emitted values:\n`reaction`, `reply`, `repost`, `mention`, `comment`, `follow`, `zap`, `delete`.\nSee `reply_context` for distinguishing top-level vs threaded replies."},"read":{"type":"integer","format":"int32","description":"Has this notification been read? (0 = unread, 1 = read)","minimum":0},"referenced_d_tag":{"type":["string","null"],"description":"Stable d-tag identifier for the referenced addressable event"},"referenced_event_id":{"type":"string","description":"The event ID that was referenced (if any)"},"referenced_event_title":{"type":["string","null"],"description":"Title of the referenced event when it is a kind 34236 video.\n`None` for other kinds, events without a `title` tag, or empty titles."},"referenced_video":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/NotificationReferencedVideo","description":"Video metadata for the referenced event (only for video kinds)"}]},"reply_context":{"type":["string","null"],"description":"Classification of the e-tag chain for `reply` notifications. One of\n`reply_to_your_comment_on_your_video`,\n`reply_to_your_comment_on_others_video`. Populated only on\n`notification_type == \"reply\"`. `None` otherwise."},"root_event_id":{"type":["string","null"],"description":"Hex event id of the root navigation target. For kind 1111 comments and\nreplies, this is the NIP-22 `E` tag (the video). For other notification\ntypes, this is `referenced_event_id` (the reaction/repost target,\nfollow subject, etc.). Clients should prefer `root_event_id` for\nnavigation and `target_comment_id` for in-thread scrolling."},"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_profile":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/NotificationSourceProfile","description":"Profile of the actor who triggered the notification"}]},"source_pubkey":{"type":"string","description":"The pubkey of the user who triggered the notification"},"target_comment_id":{"type":["string","null"],"description":"Hex event id of the specific comment to scroll to when opening the\nreferenced video's comments sheet. Populated for `comment` and `reply`\nnotifications (equals `source_event_id`). `None` for other types."}}},"NotificationReferencedVideo":{"type":"object","description":"Inline video metadata for the referenced event in a notification","required":["title"],"properties":{"d_tag":{"type":["string","null"],"description":"Stable d-tag identifier for the video"},"thumbnail":{"type":["string","null"],"description":"Thumbnail URL"},"title":{"type":"string","description":"Video title"}}},"NotificationSourceProfile":{"type":"object","description":"Inline profile for the actor who triggered a notification","required":["display_name"],"properties":{"display_name":{"type":"string","description":"Display name of the actor"},"nip05":{"type":["string","null"],"description":"NIP-05 identifier"},"picture":{"type":["string","null"],"description":"Profile picture URL"}}},"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}}},"PaginatedCommentsResponseDoc":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CommentEvent"}},"pagination":{"$ref":"#/components/schemas/PaginationMetadata"}}},"PaginatedProfileSearchResponseDoc":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProfileSearchResult"}},"pagination":{"$ref":"#/components/schemas/PaginationMetadata"}}},"PaginatedSearchResultsResponseDoc":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchResultItemDoc"}},"pagination":{"$ref":"#/components/schemas/PaginationMetadata"}}},"PaginatedUserListResponseDoc":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UserListItemDoc"}},"pagination":{"$ref":"#/components/schemas/PaginationMetadata"}}},"PaginatedUserVideosResponseDoc":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UserVideosItemDoc"}},"pagination":{"$ref":"#/components/schemas/PaginationMetadata"}}},"PaginatedVideosResponseDoc":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VideoListItemDoc"}},"pagination":{"$ref":"#/components/schemas/PaginationMetadata"}}},"PaginationMetadata":{"type":"object","required":["has_more"],"properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"PeopleListStats":{"type":"object","description":"Aggregated stats for a people list (NIP-51 kind 30000).","required":["members","videos","loops"],"properties":{"loops":{"type":"integer","format":"int64","minimum":0},"members":{"type":"integer","format":"int64","minimum":0},"videos":{"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":{"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"has_videos":{"type":["boolean","null"],"description":"Filter to users with video content"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 25)","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\", \"videos\", 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}}},"PublishEventRequest":{"type":"object","description":"A signed Nostr event body, as documented at https://github.com/nostr-protocol/nips.","required":["id","pubkey","created_at","kind","tags","content","sig"],"properties":{"content":{"type":"string","description":"Event content. JSON for kinds that require structured content; plain text otherwise."},"created_at":{"type":"integer","format":"int64","description":"Unix timestamp (seconds)."},"id":{"type":"string","description":"64-char lowercase hex event id (SHA-256 of the serialized event)."},"kind":{"type":"integer","format":"int32","description":"Event kind. Must be a kind supported by the relay (see NIP-11 `supported_nips`).","minimum":0},"pubkey":{"type":"string","description":"64-char lowercase hex pubkey of the author."},"sig":{"type":"string","description":"128-char lowercase hex Schnorr signature over the event id."},"tags":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Tags as nested string arrays (e.g. `[[\"d\", \"my-video\"], [\"title\", \"...\"], ...]`)."}}},"PublishEventRequestDoc":{"type":"object","required":["id","pubkey","created_at","kind","tags","content","sig"],"properties":{"content":{"type":"string","description":"Event content."},"created_at":{"type":"integer","format":"int64","description":"Unix timestamp."},"id":{"type":"string","description":"Event ID (hex)."},"kind":{"type":"integer","format":"int32","description":"Nostr event kind.","minimum":0},"pubkey":{"type":"string","description":"Event author pubkey (hex)."},"sig":{"type":"string","description":"Event signature (hex)."},"tags":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Event tags as Nostr tag arrays."}}},"PublishEventResponse":{"type":"object","description":"Successful REST event-publish response.","required":["event_id","accepted","message"],"properties":{"accepted":{"type":"boolean","description":"Always `true` on 2xx responses."},"event_id":{"type":"string","description":"The accepted event ID (64-char lowercase hex)."},"message":{"type":"string","description":"Human-readable message. Empty on success; rejection reason on failure."}}},"PublishEventResponseDoc":{"type":"object","required":["event_id","accepted","message"],"properties":{"accepted":{"type":"boolean","description":"Whether the event was accepted."},"event_id":{"type":"string","description":"Event ID (hex)."},"message":{"type":"string","description":"Empty on success, rejection reason on failure."}}},"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"}}}}},"RecommendationCohort":{"type":"string","enum":["classic","fresh","discovery"]},"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)"},"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"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."},"offset":{"type":["integer","null"],"format":"int32","description":"Legacy offset pagination alias","minimum":0}}},"RecommendationsResponse":{"type":"object","description":"Recommendations response.","required":["videos","source","limit","offset","has_more","fallback_applied"],"properties":{"fallback_applied":{"type":"boolean","description":"Whether fallback recommendations were used for this page"},"has_more":{"type":"boolean","description":"Whether another page is available"},"limit":{"type":"integer","format":"int32","description":"Effective limit applied to this page","minimum":0},"next_cursor":{"type":["string","null"],"description":"Preferred opaque cursor for the next page, if any"},"next_offset":{"type":["integer","null"],"format":"int32","description":"Legacy offset for the next page, if any","minimum":0},"offset":{"type":"integer","format":"int32","description":"Zero-based offset of the first item in this page","minimum":0},"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"}}},"Rsvp":{"type":"object","description":"Latest materialized NIP-52 RSVP row.","required":["id","pubkey","created_at","kind","content","coordinate","d_tag","target_coordinate","status","free_busy"],"properties":{"content":{"type":"string"},"coordinate":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"d_tag":{"type":"string"},"free_busy":{"type":"string"},"id":{"type":"string"},"kind":{"type":"integer","format":"int32","minimum":0},"pubkey":{"type":"string"},"status":{"type":"string"},"target_coordinate":{"type":"string"}}},"RsvpCounts":{"type":"object","description":"Aggregate RSVP counts for a single calendar event coordinate.","required":["accepted","declined","tentative"],"properties":{"accepted":{"type":"integer","format":"int64","minimum":0},"declined":{"type":"integer","format":"int64","minimum":0},"tentative":{"type":"integer","format":"int64","minimum":0}}},"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)"},"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"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 25)","minimum":0},"moderation_profile":{"type":["string","null"],"description":"Moderation profile: default, strict, or user."},"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\""}}},"SearchResultItemDoc":{"oneOf":[{"$ref":"#/components/schemas/VideoStats"},{"$ref":"#/components/schemas/VideoHashtag"},{"$ref":"#/components/schemas/AudioStats"}]},"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},"sha256":{"type":"string","description":"SHA256 hash of the backing video blob."},"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"}}},"UserListItemDoc":{"oneOf":[{"type":"string"},{"$ref":"#/components/schemas/UserData"}]},"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."},"UserVideosItemDoc":{"oneOf":[{"$ref":"#/components/schemas/UserVideoWithStats"},{"$ref":"#/components/schemas/VideoWithLoops"}]},"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"},"cursor":{"type":["string","null"],"description":"Opaque pagination cursor"},"limit":{"type":["integer","null"],"format":"int32","description":"Max results (1-100, default 25)","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":"Discovery/classifier labels (e.g. [\"topic:music\"]) — 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},"moderation_labels":{"type":"array","items":{"type":"string"},"description":"Resolved trust-and-safety labels for the active moderation profile."},"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},"sha256":{"type":"string","description":"SHA256 hash of the backing video blob."},"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"},"sha256":{"type":"string","description":"SHA256 hash of the backing video blob when available."},"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},"sha256":{"type":"string","description":"SHA256 hash of the backing video blob."},"thumbnail":{"type":"string"},"title":{"type":"string"},"video_url":{"type":"string"}}},"VideoListItemDoc":{"oneOf":[{"$ref":"#/components/schemas/TrendingVideo"},{"$ref":"#/components/schemas/VideoStats"},{"$ref":"#/components/schemas/VideoHashtag"},{"$ref":"#/components/schemas/VideoWithLoops"}]},"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},"sha256":{"type":"string","description":"SHA256 hash of the backing video blob."},"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":"v2","description":"Versioned list endpoints that return a unified `{ data, pagination }` envelope (recommended for new partner integrations). The legacy `/api/...` paths return their original pre-PR-#238 shapes (bare arrays or endpoint-specific wrappers) and remain stable."},{"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":"calendar","description":"NIP-52 calendar events, collections, RSVPs, and related reads"},{"name":"badges","description":"NIP-58 badge definitions and awards"},{"name":"lists","description":"NIP-51 people list stats"},{"name":"sounds","description":"Audio/sound metadata, statistics, and recommendations"},{"name":"experimental","description":"Experimental research endpoints for recommendation prototyping"},{"name":"feeds","description":"RSS 2.0 feeds with podcast namespace extensions (served at /feed/*)"},{"name":"publish","description":"REST event publishing (routed to funnelcake-relay; see /api/events)"}]}