{"components":{"responses":{},"schemas":{"AdminCommunityPostCreate":{"description":"Parameters for importing a community post","example":{"post":{"author":{"email":"jane@example.com","external_id":"rmkvKw9g","first_name":"Jane","last_name":"Doe","name":"Jane Doe"},"category":"announcements","content":"Kicking off week one — introduce yourself!","created_at":"2024-03-01T12:00:00Z","external_ref":"kajabi:post:9182","mentions":["outseta:XyZ789"],"pinned":true,"pinned_at":"2024-03-01T12:05:00Z","reactions":{"count":42},"updated_at":"2024-03-04T08:15:00Z"}},"properties":{"post":{"description":"Post parameters","properties":{"author":{"$ref":"#/components/schemas/CommunityImportAuthor"},"category":{"description":"Post category (optional, defaults to 'general')","enum":["announcements","q_and_a","general"],"example":"announcements","type":"string"},"content":{"description":"Post content (required)","example":"Kicking off week one — introduce yourself!","type":"string"},"created_at":{"description":"Original posting time, ISO-8601. Truncated to seconds. A time in the future is rejected (422). Sets `updated_at` too unless that is sent separately.","example":"2024-03-01T12:00:00Z","format":"date-time","type":"string"},"external_ref":{"description":"Idempotency key from the source system. Replaying it returns 200 with the stored post.","example":"kajabi:post:9182","type":"string"},"mentions":{"$ref":"#/components/schemas/MentionsParam"},"pinned":{"description":"Whether the post is pinned to the top of the feed (optional, defaults to false)","example":true,"type":"boolean"},"pinned_at":{"description":"When the post was pinned, ISO-8601. Orders pinned posts against each other; pinned posts without it sort last among the pinned.","example":"2024-03-01T12:05:00Z","format":"date-time","type":"string"},"reactions":{"$ref":"#/components/schemas/CommunityImportReactions"},"updated_at":{"description":"Time the source post was last edited, ISO-8601. Optional: omit it and the post reads as written once and never edited. Requires `created_at` — sent on its own it is rejected (422), because the creation time would then be stamped at import and land after the edit. A time in the future, or one before `created_at`, is rejected (422).","example":"2024-03-04T08:15:00Z","format":"date-time","type":"string"}},"required":["content","author"],"type":"object"}},"required":["post"],"title":"AdminCommunityPostCreate","type":"object"},"Mention":{"description":"A record that the current viewer was mentioned in a post or a comment","properties":{"author":{"$ref":"#/components/schemas/CommunityAuthor"},"comment_id":{"description":"The comment the mention lives in, or null when the mention was in the post itself","format":"uuid","nullable":true,"type":"string"},"created_at":{"description":"When the mention was recorded","format":"date-time","type":"string"},"excerpt":{"description":"First non-empty line of the mentioning content as plain text — markup stripped, common entities decoded — ellipsized at 140 characters. Safe to render as-is.","example":"Hey @jane — could you take a look at this?","nullable":true,"type":"string"},"id":{"description":"Mention UUID","format":"uuid","type":"string"},"post_id":{"description":"The post the mention lives in. Always present — for a mention in a comment this is the comment's post, so every row can be deep-linked.","format":"uuid","type":"string"},"read_at":{"description":"When the mention was marked read, or null when unread","format":"date-time","nullable":true,"type":"string"}},"required":["id","post_id","author","created_at"],"title":"Mention","type":"object"},"NotificationChannels":{"description":"Per-viewer notification channel switches. Both default to true.","properties":{"email":{"description":"Whether the viewer receives notifications by email.","example":true,"type":"boolean"},"push":{"description":"Whether the viewer receives notifications by push.","example":true,"type":"boolean"}},"required":["email","push"],"title":"NotificationChannels","type":"object"},"AuthSessionResponse":{"properties":{"bypassed":{"nullable":true,"type":"boolean"},"expires_at":{"format":"date-time","nullable":true,"type":"string"},"session_id":{"format":"uuid","nullable":true,"type":"string"}},"title":"AuthSessionResponse","type":"object"},"VideoTranscriptResponse":{"properties":{"language":{"description":"Transcript language code","example":"en","nullable":true,"type":"string"},"segments":{"items":{"properties":{"end":{"description":"End time in seconds","type":"number"},"speaker":{"description":"Speaker label, omitted when absent","type":"string"},"start":{"description":"Start time in seconds","type":"number"},"text":{"type":"string"}},"required":["start","end","text"],"type":"object"},"type":"array"}},"required":["language","segments"],"title":"VideoTranscriptResponse","type":"object"},"CollectionResponse":{"description":"Response containing a single collection","properties":{"data":{"$ref":"#/components/schemas/Collection"}},"required":["data"],"title":"CollectionResponse","type":"object"},"LibraryAiEvidenceSource":{"description":"An uncited, ranked evidence excerpt. IDs are deterministic within a response and reference the same source when the same ranked retrieval is repeated; they are not durable database IDs. Timestamps are integer seconds.","properties":{"attachment_filename":{"nullable":true,"type":"string"},"attachment_id":{"nullable":true,"type":"integer"},"canonical_url":{"nullable":true,"type":"string"},"collection_id":{"nullable":true,"type":"string"},"document_type":{"type":"string"},"id":{"description":"Response-scoped source reference","type":"string"},"kind":{"enum":["chunk","moment"],"type":"string"},"moment_id":{"nullable":true,"type":"string"},"playback_id":{"nullable":true,"type":"string"},"playback_token":{"description":"Fresh signed playback JWT; null for public or non-playback sources.","nullable":true,"type":"string"},"published_at":{"nullable":true,"type":"integer"},"resource_id":{"nullable":true,"type":"string"},"slug":{"nullable":true,"type":"string"},"source_type":{"enum":["transcript","resource","attachment","moment"],"type":"string"},"speaker":{"nullable":true,"type":"string"},"speaker_label":{"nullable":true,"type":"string"},"speaker_name":{"nullable":true,"type":"string"},"text":{"maxLength":2000,"type":"string"},"text_truncated":{"type":"boolean"},"thumbnail_url":{"nullable":true,"type":"string"},"timestamp":{"description":"Start time in seconds","nullable":true,"type":"integer"},"timestamp_end":{"description":"End time in seconds","nullable":true,"type":"integer"},"timestamp_url":{"nullable":true,"type":"string"},"title":{"type":"string"},"type":{"nullable":true,"type":"string"},"url":{"nullable":true,"type":"string"},"video_id":{"nullable":true,"type":"string"}},"required":["id","kind","source_type","title","text","text_truncated"],"title":"LibraryAiEvidenceSource","type":"object"},"AdminVideoResponse":{"properties":{"data":{"allOf":[{"$ref":"#/components/schemas/AdminVideo"},{"properties":{"failure_reason":{"description":"Recorded status_detail for failed video/transcription processing, humanized when it is a machine code; falls back to the failed status. Null otherwise. Not a background-job or indexing error log.","nullable":true,"type":"string"},"ready":{"description":"A transcript_json track with a nonempty URL exists and recorded RAG ingestion is completed. Does not guarantee freshness or viewer access.","type":"boolean"}},"required":["ready","failure_reason"],"type":"object"}]}},"required":["data"],"title":"AdminVideoResponse","type":"object"},"CommunityComment":{"description":"A comment on a community post","properties":{"author":{"allOf":[{"$ref":"#/components/schemas/CommunityAuthor"}],"description":"Comment author; null for deleted placeholders","nullable":true,"type":"object"},"content":{"description":"Comment content","example":"Great post! Thanks for sharing.","type":"string"},"created_at":{"description":"Creation timestamp","format":"date-time","type":"string"},"deleted_at":{"description":"Deletion timestamp for an empty-content placeholder; replies remain","format":"date-time","nullable":true,"type":"string"},"depth":{"description":"Nesting depth (0 = top-level, max 2)","example":0,"type":"integer"},"edited_at":{"description":"Last content edit; null until a member edit","format":"date-time","nullable":true,"type":"string"},"id":{"description":"Comment UUID","format":"uuid","type":"string"},"reactions":{"description":"Reactions summary (mirrors the post shape, without reacted_by)","properties":{"count":{"description":"Total number of reactions","example":5,"type":"integer"},"viewer_has_reacted":{"description":"Whether the current viewer has reacted (only when X-Viewer-ID header is provided)","nullable":true,"type":"boolean"}},"required":["count"],"type":"object"},"replies":{"description":"Nested replies (same structure as comment)","items":{"type":"object"},"type":"array"},"updated_at":{"description":"Last update timestamp","format":"date-time","type":"string"}},"required":["id","content","depth","reactions","author","created_at"],"title":"CommunityComment","type":"object"},"VideoChatResponse":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/VideoChatMessage"},"type":"array"},"reactions":{"items":{"$ref":"#/components/schemas/VideoChatReaction"},"type":"array"}},"required":["messages","reactions"],"title":"VideoChatResponse","type":"object"},"LibraryAiChatResponse":{"description":"AI answer, clarification, or retrieval-only evidence from your video library.\n\nCitation indices in the answer text (e.g., `[4]`) map to source array positions: `[4]` → `sources[3]`.\nEach source has a `cited` boolean indicating whether it's referenced in the answer.\n\nFor `response_type: retrieval`, `content`, `citations`, and synthesis `usage` are omitted. `sources`\ncontains ordered evidence and deliberately has no `cited` field because no answer was synthesized.\n","properties":{"citations":{"deprecated":true,"description":"Deprecated: Use `sources` instead. Same data as sources.","items":{"$ref":"#/components/schemas/Citation"},"type":"array"},"confidence":{"description":"Retrieval confidence; none means no evidence was found","enum":["high","medium","low","none"],"nullable":true,"type":"string"},"content":{"description":"AI response","type":"string"},"conversation_id":{"description":"Conversation ID for follow-up questions","type":"string"},"id":{"description":"Conversation ID","type":"string"},"interaction_id":{"description":"Logical turn ID. Best-effort asynchronous capture, not a persistence acknowledgement.","format":"uuid","type":"string"},"metadata":{"anyOf":[{"$ref":"#/components/schemas/LibraryAiRetrievalMetadata"}],"description":"Public-safe retrieval metadata for answer responses. Clarification responses may omit this field.","nullable":true},"questions":{"description":"Exact questions when response_type is clarification","items":{"type":"string"},"nullable":true,"type":"array"},"response_type":{"description":"Whether this is a direct answer, clarifying question, or retrieval result","enum":["answer","clarification","retrieval"],"type":"string"},"sources":{"description":"Answer citations, or ordered uncited evidence when response_type is retrieval.","items":{"oneOf":[{"$ref":"#/components/schemas/Citation"},{"$ref":"#/components/schemas/LibraryAiEvidenceSource"}]},"type":"array"}},"required":["id","conversation_id","response_type","sources"],"title":"LibraryAiChatResponse","type":"object"},"CommunityImportReactions":{"description":"Historical engagement, seeded as a count only — reactor identities are not\nimported, so an imported row reports a count with an empty `reacted_by`.\nReactions added natively afterwards still increment correctly.\n\n`viewer_has_reacted` also stays `false` for every member, because it is read\nfrom reaction rows rather than the count: a member's first real reaction\nturns 42 into 43 instead of correcting it. Prefer omitting this field over\npublishing a count nobody can act on.\n","properties":{"count":{"description":"Reaction count","example":42,"minimum":0,"type":"integer"}},"required":["count"],"title":"CommunityImportReactions","type":"object"},"MentionsResponse":{"description":"Mentions of the current viewer, newest first","properties":{"data":{"description":"List of mentions","items":{"$ref":"#/components/schemas/Mention"},"type":"array"},"meta":{"description":"Pagination metadata","properties":{"page":{"description":"Current page number (1-indexed)","example":1,"type":"integer"},"page_size":{"description":"Items per page","example":20,"type":"integer"},"total_entries":{"description":"Total mentions","example":7,"type":"integer"},"total_pages":{"description":"Total number of pages","example":1,"type":"integer"}},"required":["page","page_size","total_entries","total_pages"],"type":"object"}},"required":["data","meta"],"title":"MentionsResponse","type":"object"},"AdminCollectionCreateRequest":{"allOf":[{"$ref":"#/components/schemas/AdminCollectionRequest"}],"required":["name"],"title":"AdminCollectionCreateRequest","type":"object"},"CommunityPostsResponse":{"description":"Response containing a list of community posts","properties":{"data":{"description":"List of community posts","items":{"$ref":"#/components/schemas/CommunityPost"},"type":"array"},"meta":{"description":"Pagination metadata","properties":{"page":{"description":"Current page number (1-indexed)","example":1,"type":"integer"},"page_size":{"description":"Items per page","example":20,"type":"integer"},"total_entries":{"description":"Total number of items across all pages","example":47,"type":"integer"},"total_pages":{"description":"Total number of pages","example":3,"type":"integer"}},"required":["page","page_size","total_entries","total_pages"],"type":"object"}},"required":["data","meta"],"title":"CommunityPostsResponse","type":"object"},"RevokeAuthSessionResponse":{"properties":{"ok":{"type":"boolean"},"session_id":{"format":"uuid","type":"string"}},"required":["ok","session_id"],"title":"RevokeAuthSessionResponse","type":"object"},"SessionManagementViewerState":{"properties":{"active_session_count":{"minimum":0,"type":"integer"},"admin_overrides_allowed":{"type":"boolean"},"can_update_device_limit_override":{"description":"Whether this tenant key can set or clear the viewer's numeric override.","type":"boolean"},"can_update_session_management_exemption":{"description":"Whether this tenant key can set or clear the viewer's exemption flag.","type":"boolean"},"device_limit_override":{"minimum":1,"nullable":true,"type":"integer"},"effective_device_limit":{"minimum":1,"nullable":true,"type":"integer"},"effective_device_limit_source":{"enum":["policy","override","exempt"],"type":"string"},"external_id":{"type":"string"},"policy_default_device_limit":{"minimum":1,"type":"integer"},"session_management_exempt":{"type":"boolean"},"viewer_id":{"format":"uuid","type":"string"}},"required":["viewer_id","external_id","active_session_count","policy_default_device_limit","effective_device_limit","effective_device_limit_source","device_limit_override","session_management_exempt","admin_overrides_allowed","can_update_device_limit_override","can_update_session_management_exemption"],"title":"SessionManagementViewerState","type":"object"},"AdminSessionManagementExemptionResponse":{"properties":{"data":{"properties":{"device_limit_override":{"nullable":true,"type":"integer"},"session_management_exempt":{"type":"boolean"},"viewer_id":{"format":"uuid","type":"string"}},"required":["viewer_id","session_management_exempt"],"type":"object"}},"required":["data"],"title":"AdminSessionManagementExemptionResponse","type":"object"},"AudienceEventRequest":{"description":"Dedicated best-effort audience beacon. `source_open` establishes an open and its\nserver-received first-open time. `video_progress` requires that explicit open.\nBeacons are always acknowledged with 202; this is not a durable acknowledgement.\n","properties":{"interaction_id":{"description":"Interaction returned by the AI/search response that supplied this video.","format":"uuid","type":"string"},"n":{"enum":["source_open","video_progress"],"type":"string"},"playback_id":{"description":"Client-generated UUID for this individual open. Generate a new UUID per open; this is not a Mux asset playback ID.","format":"uuid","type":"string"},"vid":{"description":"Video ID or slug referenced by the source.","type":"string"},"viewer":{"description":"Optional existing viewer UUID or external ID. It must resolve to the viewer on the interaction; omit only for anonymous interactions.","type":"string"},"watched_seconds":{"description":"For video_progress: cumulative elapsed PLAYING wall-clock seconds for this open. Explicit 0 is valid; omit when there is no watch sample. Do not derive from playhead position, seeks, pauses, or citation timestamps.","format":"float","maximum":31536000,"minimum":0,"type":"number"}},"required":["n","interaction_id","playback_id","vid"],"title":"AudienceEventRequest","type":"object"},"AdminVideoIngestResponse":{"allOf":[{"$ref":"#/components/schemas/AdminVideoResponse"},{"properties":{"deduplicated":{"description":"False on 201, true on 200. Existing metadata is never changed by a replay.","type":"boolean"}},"required":["deduplicated"],"type":"object"}],"title":"AdminVideoIngestResponse"},"AdminRevokeAuthSessionRequest":{"properties":{"reason":{"nullable":true,"type":"string"}},"title":"AdminRevokeAuthSessionRequest","type":"object"},"AdminCollectionVideosRequest":{"properties":{"video_ids":{"description":"Video internal UUIDs (preferred), legacy hashids or slugs. Duplicates are ignored.","items":{"type":"string"},"type":"array"}},"required":["video_ids"],"title":"AdminCollectionVideosRequest","type":"object"},"ConversationVideoContext":{"description":"Lightweight video metadata for conversation listings","properties":{"id":{"description":"Video external ID (hashid or UUID per account settings)","type":"string"},"thumbnail_url":{"description":"Video thumbnail URL","nullable":true,"type":"string"},"title":{"description":"Video title","type":"string"},"url":{"description":"Video page URL on portal","nullable":true,"type":"string"}},"required":["id","title"],"title":"ConversationVideoContext","type":"object"},"VideoConversationResponse":{"properties":{"conversation_id":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"messages":{"items":{"properties":{"content":{"type":"string"},"id":{"type":"string"},"inserted_at":{"format":"date-time","type":"string"},"interaction_id":{"description":"Persisted interaction linked to this exact assistant message. Null for other roles or when no unique eligible capture exists, including pending capture, legacy messages, and erased interactions.","format":"uuid","nullable":true,"type":"string"},"role":{"enum":["user","assistant"],"type":"string"},"sources":{"items":{"type":"object"},"type":"array"}},"type":"object"},"type":"array"},"metadata":{"type":"object"},"updated_at":{"format":"date-time","type":"string"},"video_id":{"type":"string"}},"required":["conversation_id","video_id","messages","created_at","updated_at"],"title":"VideoConversationResponse","type":"object"},"AdminVideoUpdateRequest":{"additionalProperties":false,"properties":{"add_collection_ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"add_tags":{"items":{"minLength":1,"type":"string"},"type":"array"},"collection_ids":{"description":"Replace all memberships","items":{"format":"uuid","type":"string"},"type":"array"},"description":{"nullable":true,"type":"string"},"privacy":{"enum":["public","unlisted","private"],"type":"string"},"published_at":{"format":"date-time","nullable":true,"type":"string"},"remove_collection_ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"remove_tags":{"items":{"minLength":1,"type":"string"},"type":"array"},"tags":{"description":"Replace all tags","items":{"minLength":1,"type":"string"},"type":"array"},"title":{"minLength":3,"type":"string"},"video_type":{"description":"System/account video type UUID or slug; account slug takes precedence","type":"string"}},"title":"AdminVideoUpdateRequest","type":"object"},"RegisterDeviceResponse":{"example":{"ok":true,"provider":"expo","session_id":"0b2f2e3f-7f1e-4a5f-9f1a-6c1f2d3e4a5b"},"properties":{"ok":{"type":"boolean"},"provider":{"enum":["expo","fcm","apns"],"type":"string"},"session_id":{"format":"uuid","type":"string"}},"required":["ok","session_id","provider"],"title":"RegisterDeviceResponse","type":"object"},"ViewerUpdate":{"description":"Parameters for updating a viewer","properties":{"avatar_url":{"description":"URL to avatar image","example":"https://example.com/avatars/john.jpg","format":"uri","nullable":true,"type":"string"},"display_name":{"description":"Preferred display name","example":"Johnny D","nullable":true,"type":"string"},"email":{"description":"Viewer email","example":"john@example.com","format":"email","type":"string"},"external_id":{"description":"External ID from course platform","example":"usr_123abc","nullable":true,"type":"string"},"first_name":{"description":"First name","example":"John","nullable":true,"type":"string"},"last_name":{"description":"Last name","example":"Doe","nullable":true,"type":"string"},"name":{"description":"Viewer name","example":"John Doe","type":"string"},"traits":{"description":"Custom traits map","example":{"business_name":"Acme Corp","stage":"growth"},"nullable":true,"type":"object"}},"title":"ViewerUpdate","type":"object"},"CommunityImportAuthor":{"description":"Author attribution by the customer's own member id. Bold looks up a viewer\nwith this `external_id` and creates one if it has never seen them. An\nexisting viewer is **never** modified, so the remaining fields only take\neffect on creation.\n\nSend `external_id` exactly as your app sends it when it provisions a viewer\nat login — the raw member id from your identity provider, with no prefix or\nother decoration. A value that differs by so much as a prefix creates a\nsecond viewer for the same person the first time they sign in.\n\nSend `name` as well as `first_name` / `last_name`: when `name` is omitted it\nfalls back to the `external_id`, so the raw member id ends up in the\nviewer's name.\n","properties":{"avatar_url":{"format":"uri","nullable":true,"type":"string"},"display_name":{"description":"Display name for a newly created viewer","example":"Jane D.","nullable":true,"type":"string"},"email":{"format":"email","nullable":true,"type":"string"},"external_id":{"description":"Customer member id, exactly as sent at login (e.g. the bare Outseta Person UID)","example":"rmkvKw9g","type":"string"},"first_name":{"example":"Jane","nullable":true,"type":"string"},"last_name":{"example":"Doe","nullable":true,"type":"string"},"name":{"description":"Full name for a newly created viewer; falls back to external_id","example":"Jane Doe","nullable":true,"type":"string"}},"required":["external_id"],"title":"CommunityImportAuthor","type":"object"},"VideoIngestRequest":{"properties":{"callback_url":{"description":"HTTPS URL to receive a webhook POST when video processing completes or fails. Must use HTTPS.","nullable":true,"type":"string"},"collection_id":{"description":"Collection to add the video to","format":"uuid","type":"string"},"description":{"description":"Video description","type":"string"},"external_id":{"description":"Caller's reference ID. Used for idempotency within tenant.","type":"string"},"language":{"description":"ISO 639-1 language code (default: \"en\")","type":"string"},"source_url":{"description":"URL to download the video from","type":"string"},"tags":{"description":"Tag names to assign (created if they don't exist)","items":{"type":"string"},"type":"array"},"title":{"description":"Video title","type":"string"}},"required":["source_url","title"],"title":"VideoIngestRequest","type":"object"},"MentionsReadResponse":{"description":"How many mentions were flipped, and the resulting badge count","example":{"data":{"marked_read":2,"unread_count":0}},"properties":{"data":{"properties":{"marked_read":{"description":"Mentions that moved from unread to read in this call","example":2,"type":"integer"},"unread_count":{"description":"Unread mentions remaining after the call","example":0,"type":"integer"}},"required":["marked_read","unread_count"],"type":"object"}},"required":["data"],"title":"MentionsReadResponse","type":"object"},"AiStreamEvent":{"description":"Server-Sent Event for streaming AI responses.\n\nEvent types:\n- `message_start`: Initial event with conversation_id and video_id\n- `conversation_created`: Conversation record created\n- `image_analysis`: Uploaded image analysis result\n- `progress`: Pipeline stage status\n- `sources`: Retrieved sources\n- `citation_map`: Stable citation map for streamed text\n- `clarification`: Clarifying question\n- `text_delta`: Incremental text chunk\n- `answer`: Complete library answer with citations and public-safe retrieval metadata\n- `message_complete`: Final event with a synthesized answer, clarification, or retrieval result\n- `error`: Error event with code and message\n- `complete`: Stream termination event\n","properties":{"code":{"description":"Error code (for error events)","nullable":true,"type":"string"},"confidence":{"enum":["high","medium","low","none"],"nullable":true,"type":"string"},"content":{"description":"Full content (for message_complete)","nullable":true,"type":"string"},"conversation_id":{"nullable":true,"type":"string"},"delta":{"description":"Text chunk (for text_delta)","nullable":true,"type":"string"},"id":{"description":"Conversation ID on retrieval-only message_complete events","nullable":true,"type":"string"},"interaction_id":{"description":"Logical action ID on message_complete; null for search previews. Older servers may omit it.","format":"uuid","nullable":true,"type":"string"},"message":{"description":"Error message (for error events)","nullable":true,"type":"string"},"metadata":{"anyOf":[{"$ref":"#/components/schemas/LibraryAiRetrievalMetadata"}],"description":"Public-safe retrieval metadata on library `answer` events. Internal retrieval diagnostics are not exposed.","nullable":true},"model":{"nullable":true,"type":"string"},"questions":{"description":"Exact clarification questions when response_type=clarification","items":{"type":"string"},"nullable":true,"type":"array"},"response_type":{"description":"Terminal response discriminator","enum":["answer","clarification","retrieval"],"nullable":true,"type":"string"},"retryable":{"nullable":true,"type":"boolean"},"sources":{"description":"Answer citations or, when response_type=retrieval, ordered uncited evidence excerpts","items":{"anyOf":[{"$ref":"#/components/schemas/Citation"},{"$ref":"#/components/schemas/LibraryAiEvidenceSource"},{"$ref":"#/components/schemas/LibraryAiStreamSource"}]},"nullable":true,"type":"array"},"stage":{"description":"Pipeline stage for progress events","nullable":true,"type":"string"},"type":{"description":"Event type","enum":["message_start","conversation_created","image_analysis","progress","sources","citation_map","clarification","text_delta","answer","message_complete","error","complete"],"type":"string"},"usage":{"nullable":true,"type":"object"},"video_id":{"nullable":true,"type":"string"}},"required":["type"],"title":"AiStreamEvent","type":"object"},"DeviceLimitError":{"properties":{"active_sessions":{"items":{"$ref":"#/components/schemas/ActiveSessionSummary"},"type":"array"},"error":{"enum":["device_limit_exceeded"],"type":"string"}},"required":["error"],"title":"DeviceLimitError","type":"object"},"SearchErrorResponse":{"description":"Search failure response returned by the search controller.","properties":{"details":{"description":"Search backend error details","example":"The Content-Type `application/json,application/json` is invalid.","type":"string"},"error":{"example":"Search failed","type":"string"}},"required":["error","details"],"title":"SearchErrorResponse","type":"object"},"VoiceAllowanceError":{"description":"The account has used its voice minutes for the current calendar month (UTC). Sessions already running finish; new ones start again on `resets_on`.","properties":{"code":{"example":"voice_allowance_exceeded","type":"string"},"message":{"example":"This account has used its voice minutes for this month.","type":"string"},"resets_on":{"description":"The day the allowance resets: the first of next month, UTC","example":"2026-10-01","format":"date","type":"string"},"retryable":{"example":false,"type":"boolean"},"type":{"example":"error","type":"string"}},"required":["code","message","resets_on"],"title":"VoiceAllowanceError","type":"object"},"Error":{"description":"API error response","properties":{"code":{"description":"Machine-readable error code","example":"not_found","type":"string"},"message":{"description":"Human-readable error message","example":"Video not found","type":"string"},"retryable":{"description":"Whether the request can be retried","example":false,"type":"boolean"},"type":{"description":"Error type","example":"error","type":"string"}},"required":["code","message"],"title":"Error","type":"object"},"VerifyAuthChallengeRequest":{"properties":{"code":{"description":"Six-digit verification code from the latest challenge email.","pattern":"^\\d{6}$","type":"string"}},"required":["code"],"title":"VerifyAuthChallengeRequest","type":"object"},"ThemeTokens":{"description":"OKLCH color tokens for a theme mode (light or dark)","properties":{"accent":{"description":"Accent color (OKLCH)","type":"string"},"accent_foreground":{"description":"Accent foreground color (OKLCH)","type":"string"},"background":{"description":"Background color (OKLCH)","type":"string"},"border":{"description":"Border color (OKLCH)","type":"string"},"foreground":{"description":"Foreground/text color (OKLCH)","type":"string"},"muted":{"description":"Muted background color (OKLCH)","type":"string"},"muted_foreground":{"description":"Muted text color (OKLCH)","type":"string"},"primary":{"description":"Optional sidebar primary color for buttons/headers (OKLCH)","nullable":true,"type":"string"},"ring":{"description":"Focus ring color (OKLCH)","type":"string"},"sidebar":{"description":"Optional sidebar background color (OKLCH)","nullable":true,"type":"string"},"sidebarAccent":{"description":"Optional sidebar accent color for section headers (OKLCH)","nullable":true,"type":"string"},"surface":{"description":"Surface/card color (OKLCH)","type":"string"}},"title":"ThemeTokens","type":"object"},"AdminSessionManagementExemptionRequest":{"properties":{"exempt":{"description":"Whether this viewer is exempt from session-management enforcement.","type":"boolean"}},"required":["exempt"],"title":"AdminSessionManagementExemptionRequest","type":"object"},"Video":{"description":"A video resource","properties":{"chapters":{"description":"AI-generated chapter data","items":{"type":"object"},"nullable":true,"type":"array"},"storyboard_token":{"description":"Mux storyboard JWT for signed videos with a nonempty Mux playback ID; null otherwise.","nullable":true,"type":"string"},"cta":{"description":"Effective call-to-action for this video","nullable":true,"type":"object"},"thumbnail":{"description":"Thumbnail URL","nullable":true,"type":"string"},"attachments":{"description":"Files attached to this video. Populated on single-video responses and video list responses when attachments are present.","items":{"$ref":"#/components/schemas/Attachment"},"type":"array"},"type":{"description":"Resource type","example":"video","type":"string"},"teaser":{"description":"Short teaser text","nullable":true,"type":"string"},"playback_speed":{"description":"Default playback speed","nullable":true,"type":"number"},"duration":{"description":"Duration in seconds","example":120,"type":"integer"},"playback_mode":{"description":"How the public player should source the stream. \"native\" plays from Bold; \"source\" embeds a linked source provider (for example, YouTube). Always present.","enum":["native","source"],"example":"native","type":"string"},"captions_lang":{"description":"Caption language code","nullable":true,"type":"string"},"source_provider":{"description":"Linked playback provider key. Included only when playback_mode is \"source\" and a source is available.","example":"youtube","nullable":true,"type":"string"},"captions":{"description":"Caption file URL","nullable":true,"type":"string"},"subtitles":{"description":"Subtitle track metadata","nullable":true,"type":"object"},"chapters_url":{"description":"Signed URL for chapter data","nullable":true,"type":"string"},"internal_id":{"description":"Internal UUID","example":"550e8400-e29b-41d4-a716-446655440000","format":"uuid","type":"string"},"source_id":{"description":"Linked provider's video ID. Included only when playback_mode is \"source\" and a source is available.","example":"dQw4w9WgXcQ","nullable":true,"type":"string"},"updated_at":{"description":"Last update timestamp","format":"date-time","type":"string"},"progress":{"description":"Viewer's watch progress (only present when viewer_id is provided)","nullable":true,"oneOf":[{"$ref":"#/components/schemas/VideoProgress"}]},"imported_from":{"description":"Original import source when available","nullable":true,"type":"string"},"description":{"description":"Full video description","nullable":true,"type":"string"},"id":{"description":"Video ID (hashid)","example":"abc123","type":"string"},"playback_policy":{"enum":["public","signed"],"type":"string"},"legacy_video_url":{"description":"Legacy source video URL when available","nullable":true,"type":"string"},"playback_token":{"description":"Mux playback JWT for signed videos with a nonempty Mux playback ID; null otherwise. Re-fetch the video to renew.","nullable":true,"type":"string"},"playback_id":{"description":"Mux playback ID","nullable":true,"type":"string"},"tags":{"description":"Tags assigned to this video","items":{"type":"object"},"type":"array"},"languages":{"description":"Available languages for this video: the source language plus any ready translations. Always present; a video with no translations returns just its source entry.","items":{"properties":{"code":{"description":"Language code","example":"es","type":"string"},"mux_audio_track_id":{"description":"Mux audio track reference (null for the source language)","nullable":true,"type":"string"},"mux_subtitle_track_id":{"description":"Mux subtitle track reference (null for the source language)","nullable":true,"type":"string"},"name":{"description":"Display name","example":"Spanish","type":"string"},"ready":{"description":"Whether this language is playable","type":"boolean"},"role":{"description":"Whether this is the original language or a translation","enum":["source","translation"],"type":"string"},"subtitle_url":{"description":"Subtitle (VTT) URL for this language","nullable":true,"type":"string"}},"type":"object"},"type":"array"},"transcript":{"description":"Transcript track URLs keyed by format","type":"object"},"meta_data":{"description":"Embeddable metadata for title, description, and image","type":"object"},"download_urls":{"description":"Available download URLs keyed by quality or asset type","nullable":true,"type":"object"},"stream_url":{"description":"Playable stream URL","nullable":true,"type":"string"},"playback_token_expires_at":{"description":"Expiration of playback_token in ISO8601; null when no token is issued.","format":"date-time","nullable":true,"type":"string"},"slug":{"description":"URL-friendly identifier (optional, unique)","example":"introduction-to-bold","nullable":true,"type":"string"},"captions_label":{"description":"Caption language label","nullable":true,"type":"string"},"published_at":{"description":"Publication timestamp","format":"date-time","nullable":true,"type":"string"},"title":{"description":"Video title","example":"Introduction to BOLD","type":"string"},"created_at":{"description":"Creation timestamp","format":"date-time","type":"string"}},"required":["id","title"],"title":"Video","type":"object"},"VideoChatMessage":{"properties":{"author":{"description":"Participant name rendered by account policy","type":"string"},"id":{"description":"Canonical message ID used by replies","type":"string"},"offset_ms":{"description":"Playback offset in milliseconds","minimum":0,"type":"integer"},"reply_to":{"description":"ID of the parent message in this response when this is a reply","nullable":true,"type":"string"},"text":{"description":"Message text","type":"string"}},"required":["id","offset_ms","author","text","reply_to"],"title":"VideoChatMessage","type":"object"},"UnifiedConversationListResponse":{"description":"Paginated list of all conversations for a viewer across all scopes","properties":{"conversations":{"items":{"$ref":"#/components/schemas/UnifiedConversationSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/ConversationPagination"}},"required":["conversations","pagination"],"title":"UnifiedConversationListResponse","type":"object"},"SessionManagementRevokeSessionResponse":{"properties":{"ok":{"type":"boolean"},"session_id":{"format":"uuid","type":"string"}},"required":["ok","session_id"],"title":"SessionManagementRevokeSessionResponse","type":"object"},"AiSearchRequest":{"description":"Search request with optional AI synthesis","example":{"limit":10,"prompt":"authentication setup"},"properties":{"channel":{"description":"Optional analytics channel. Missing/unrecognized values become `unknown`; metadata never rejects the search.","enum":["portal","embed","api","mcp"],"type":"string"},"client_name":{"description":"Optional analytics client name; trimmed 1–128 characters and at most 512 UTF-8 bytes. Invalid values become unknown.","maxLength":128,"minLength":1,"type":"string"},"client_version":{"description":"Optional analytics client version; trimmed 1–128 characters and at most 512 UTF-8 bytes. Invalid values become unknown.","maxLength":128,"minLength":1,"type":"string"},"collection_id":{"description":"Filter to a specific collection (legacy single-value filter)","nullable":true,"type":"string"},"collection_ids":{"description":"Filter to multiple collections (OR semantics)","items":{"type":"string"},"nullable":true,"type":"array"},"images":{"description":"Optional array of images (max 5). Requires the Vision feature flag. Each image can be base64-encoded or a URL.","items":{"anyOf":[{"$ref":"#/components/schemas/AiImageBase64"},{"$ref":"#/components/schemas/AiImageUrl"}]},"maxItems":5,"nullable":true,"type":"array"},"limit":{"description":"Maximum sources to return (default: 15)","example":10,"type":"integer"},"prompt":{"description":"Search query","example":"How do I set up authentication?","type":"string"},"request_id":{"description":"Caller action UUID. Reuse unchanged on retries for analytics dedupe; not response replay.","format":"uuid","type":"string"},"search_mode":{"default":"settled","description":"Use preview for typeahead: no interaction is recorded. Settle only on submit or result selection.","enum":["preview","settled"],"type":"string"},"stream":{"default":true,"description":"Stream response via SSE (default: true)","type":"boolean"},"tags":{"description":"Filter by video tags","items":{"type":"string"},"nullable":true,"type":"array"},"video_id":{"description":"Filter to a specific video","nullable":true,"type":"string"},"viewer":{"description":"Existing viewer UUID or external ID, resolved within the authenticated tenant. Unresolved viewers are not captured anonymously.","type":"string"}},"required":["prompt"],"title":"AiSearchRequest","type":"object"},"AdminDeviceLimitRequest":{"properties":{"limit":{"minimum":1,"nullable":true,"type":"integer"}},"title":"AdminDeviceLimitRequest","type":"object"},"Attachment":{"description":"A file attached to a video","properties":{"description":{"description":"Optional attachment description","nullable":true,"type":"string"},"file_size":{"description":"Attachment size in bytes","example":42000,"nullable":true,"type":"integer"},"file_url":{"description":"Public URL for downloading the attachment","example":"https://uploads.eu1.boldvideo.io/uploads/bt_demo/attachments/file.pdf","format":"uri","type":"string"},"id":{"description":"Attachment ID","example":123,"type":"integer"},"mime_type":{"description":"Attachment MIME type","example":"application/pdf","nullable":true,"type":"string"},"title":{"description":"Attachment title or original filename","example":"Strategy worksheet.pdf","type":"string"}},"required":["id","title","file_url"],"title":"Attachment","type":"object"},"CommunityAuthor":{"description":"Author information for community posts and comments","properties":{"avatar_url":{"description":"Author avatar URL","example":"https://example.com/avatar.jpg","format":"uri","nullable":true,"type":"string"},"id":{"description":"Author UUID","format":"uuid","type":"string"},"name":{"description":"Author display name","example":"John Doe","type":"string"}},"required":["id","name"],"title":"CommunityAuthor","type":"object"},"AiRecommendationsResponse":{"description":"AI-curated video recommendations","properties":{"guidance":{"description":"AI explanation of why these videos were recommended","nullable":true,"type":"string"},"recommendations":{"description":"Topic-ranked recommendations with playback credentials","items":{"properties":{"position":{"type":"integer"},"topic":{"type":"string"},"videos":{"items":{"properties":{"playback_id":{"nullable":true,"type":"string"},"playback_token":{"description":"Fresh signed playback JWT; null for public playback.","nullable":true,"type":"string"},"relevance":{"type":"number"},"title":{"type":"string"},"video_id":{"type":"string"},"why":{"nullable":true,"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"type":"array"},"sources":{"description":"Source citations from video content","items":{"$ref":"#/components/schemas/Citation"},"type":"array"}},"required":["recommendations"],"title":"AiRecommendationsResponse","type":"object"},"AdminErrorResponse":{"properties":{"details":{"additionalProperties":true,"description":"Field-level validation errors when present","nullable":true,"type":"object"},"error":{"description":"Machine-readable admin API error code","type":"string"},"status":{"nullable":true,"type":"integer"},"timestamp":{"format":"date-time","nullable":true,"type":"string"}},"required":["error"],"title":"AdminErrorResponse","type":"object"},"SessionManagementViewerSessionsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SessionManagementSessionWithRevocation"},"type":"array"},"viewer":{"$ref":"#/components/schemas/SessionManagementViewerState"}},"required":["viewer","data"],"title":"SessionManagementViewerSessionsResponse","type":"object"},"VideoIngestResponse":{"properties":{"callback_status":{"description":"Callback delivery status","enum":["pending","delivered","failed","blocked"],"nullable":true,"type":"string"},"callback_url":{"description":"Webhook URL for processing callbacks","nullable":true,"type":"string"},"created_at":{"description":"Creation timestamp","format":"date-time","type":"string"},"external_id":{"description":"Caller's reference ID","nullable":true,"type":"string"},"id":{"description":"Video UUID","format":"uuid","type":"string"},"status":{"description":"Current processing status","type":"string"}},"required":["id","status","created_at"],"title":"VideoIngestResponse","type":"object"},"VoiceSessionRequest":{"description":"Start a spoken conversation about one video. Bold chooses the model, voice, and instructions; they cannot be set in the request.","properties":{"sdp":{"description":"The browser's WebRTC SDP offer (starts with `v=0`)","example":"v=0\r\no=- 4611731400430051336 2 IN IP4 127.0.0.1\r\n...","maxLength":100000,"type":"string"},"video_id":{"description":"Video UUID, ID, or slug","example":"98936fa0-6826-48cc-aac1-89f42235dc8e","type":"string"},"viewer":{"description":"Viewer identifier (UUID or your external ID). When the viewers feature is enabled, the viewer's traits personalize the conversation. Forward from an authenticated server session; this field does not prove entitlement. Omitted viewers share one anonymous concurrency slot per account.","nullable":true,"type":"string"},"viewer_profile":{"anyOf":[{"description":"Free text or markdown describing the viewer; replaces the synced viewer's traits for this session. Up to 8000 characters.","example":"Sarah runs a 3-person agency, pre-launch, wants her first 10 customers.","maxLength":8000,"type":"string"},{"additionalProperties":{"anyOf":[{"maxLength":2000,"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"Key-value traits merged over the synced viewer's traits (inline wins per key). Max 20 keys; keys must match ^[a-zA-Z_][a-zA-Z0-9_]*$; values are strings (up to 2000 characters), numbers, or booleans.","example":{"business_name":"Acme Corp","stage":"growth"},"maxProperties":20,"type":"object"}],"description":"Tell the AI about the listener, for this session only (never persisted). Requires the viewers feature.","nullable":true}},"required":["video_id","sdp"],"title":"VoiceSessionRequest","type":"object"},"ActiveSessionSummary":{"properties":{"device_id":{"type":"string"},"device_label":{"description":"Server-derived human-readable label for the device/browser and OS (e.g. \"Chrome · macOS\", \"Safari · iPhone\"). Derived from the `user_agent` supplied at session creation. Nullable when the user_agent is absent or unrecognised; callers should fall back to `device_id` for display.","nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"last_seen_at":{"format":"date-time","type":"string"},"platform":{"type":"string"}},"title":"ActiveSessionSummary","type":"object"},"MentionsReadRequest":{"description":"Which mentions to mark read. Send `ids` to mark specific mentions, or `all: true`\nto mark every unread mention of the current viewer. Ids belonging to another\nviewer are ignored.\n","example":{"ids":["6b3d0d3e-2b0a-4c66-9a2f-6c1e0f4a7b21"]},"properties":{"all":{"description":"Set true to mark every unread mention read","example":true,"type":"boolean"},"ids":{"description":"Mention UUIDs to mark read","items":{"format":"uuid","type":"string"},"type":"array"}},"title":"MentionsReadRequest","type":"object"},"ConversationPagination":{"properties":{"has_more":{"type":"boolean"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"required":["limit","offset","has_more"],"title":"ConversationPagination","type":"object"},"SessionManagementRevokeSessionRequest":{"properties":{"reason":{"maxLength":255,"nullable":true,"type":"string"}},"title":"SessionManagementRevokeSessionRequest","type":"object"},"AdminImportRun":{"properties":{"counts":{"properties":{"canceled":{"type":"integer"},"done":{"description":"Created and handed off to video processing; not AI readiness","type":"integer"},"failed":{"type":"integer"},"queued":{"type":"integer"},"running":{"type":"integer"},"skipped":{"description":"Duplicates; existing videos are not republished or retyped","type":"integer"},"total":{"description":"Null until source expansion finishes","nullable":true,"type":"integer"}},"required":["total","queued","running","done","failed","skipped","canceled"],"type":"object"},"finished_at":{"description":"Null while work is active, including cancellation drain","format":"date-time","nullable":true,"type":"string"},"meta":{"properties":{"page":{"type":"integer"},"page_size":{"type":"integer"},"total_entries":{"type":"integer"},"total_pages":{"type":"integer"}},"type":"object"},"run_id":{"format":"uuid","type":"string"},"source_url":{"type":"string"},"started_at":{"description":"Time the run was accepted","format":"date-time","type":"string"},"status":{"enum":["queued","running","completed","failed","canceled"],"type":"string"},"status_url":{"type":"string"},"videos":{"items":{"properties":{"error":{"description":"Safe failure summary","nullable":true,"type":"string"},"internal_id":{"format":"uuid","nullable":true,"type":"string"},"source_url":{"type":"string"},"status":{"enum":["queued","running","done","failed","skipped","canceled"],"type":"string"}},"required":["source_url","status"],"type":"object"},"type":"array"}},"required":["run_id","source_url","status","status_url","counts","started_at","finished_at"],"title":"AdminImportRun","type":"object"},"Collection":{"description":"A video collection that can be nested in a hierarchy","properties":{"children":{"description":"Child collections (only present when include=children or include=descendants)","items":{"description":"Nested Collection object","type":"object"},"nullable":true,"type":"array"},"children_count":{"description":"Number of direct child collections","example":3,"type":"integer"},"created_at":{"description":"UTC creation timestamp (without timezone suffix)","type":"string"},"id":{"description":"Collection UUID","format":"uuid","type":"string"},"name":{"description":"Collection name","example":"Getting Started","type":"string"},"parent_id":{"description":"Parent collection UUID (null for root collections)","format":"uuid","nullable":true,"type":"string"},"position":{"description":"Position within parent (0-indexed)","example":0,"type":"integer"},"slug":{"description":"URL-friendly slug","example":"getting-started","type":"string"},"updated_at":{"description":"UTC last-update timestamp (without timezone suffix)","type":"string"},"video_count":{"description":"Public video count on collection details; all memberships on collection listings and admin writes","example":5,"type":"integer"}},"required":["id","name","slug"],"title":"Collection","type":"object"},"ProjectSettings":{"description":"Public configuration for your video portal","properties":{"data":{"description":"Settings data","properties":{"account":{"description":"Per-account capability flags","properties":{"multimodal":{"$ref":"#/components/schemas/MultimodalSettings"},"voice":{"$ref":"#/components/schemas/VoiceSettings"}},"type":"object"},"ai_avatar":{"description":"AI assistant avatar URL","nullable":true,"type":"string"},"ai_greeting":{"description":"AI greeting message","nullable":true,"type":"string"},"ai_name":{"description":"AI assistant name","nullable":true,"type":"string"},"chat_disclaimer":{"description":"Disclaimer text displayed below the AI chat input","nullable":true,"type":"string"},"favicon_url":{"description":"Favicon URL","nullable":true,"type":"string"},"has_ai":{"description":"Whether AI features are enabled","type":"boolean"},"logo_url":{"description":"Logo URL","nullable":true,"type":"string"},"name":{"description":"Account name","type":"string"},"portal":{"$ref":"#/components/schemas/PortalSettings"},"slug":{"description":"Account slug","type":"string"},"theme_config":{"$ref":"#/components/schemas/ThemeConfig"}},"type":"object"},"version":{"description":"API version","example":"0.1","type":"string"}},"required":["version","data"],"title":"ProjectSettings","type":"object"},"AdminAuthPolicyUpdateRequest":{"properties":{"admin_override_per_user":{"type":"boolean"},"allow_member_self_manage":{"type":"boolean"},"auth_challenge_product_name":{"nullable":true,"type":"string"},"auth_challenge_reply_to_email":{"format":"email","nullable":true,"type":"string"},"auth_challenge_sender_name":{"nullable":true,"type":"string"},"auth_challenge_support_url":{"format":"uri","nullable":true,"type":"string"},"auth_challenge_template_id":{"nullable":true,"type":"string"},"enforcement_mode":{"enum":["block_new","kick_oldest","challenge_new"],"type":"string"},"enforcement_phase":{"enum":["off","shadow","enforce"],"type":"string"},"impossible_travel_mode":{"description":"Impossible travel detection mode. off disables detection.","enum":["off","flag_only","challenge"],"type":"string"},"impossible_travel_speed_kmh":{"description":"Speed threshold in km/h above which a login is flagged as impossible travel.","minimum":1,"type":"integer"},"max_devices_per_user":{"minimum":1,"type":"integer"},"session_ttl_days":{"minimum":1,"type":"integer"}},"title":"AdminAuthPolicyUpdateRequest","type":"object"},"LibraryAiRetrievalMetadata":{"description":"Public-safe retrieval status metadata for library AI answers. Internal stage names, timeouts, query counts, and fallback timing are intentionally omitted.","properties":{"degraded":{"description":"True when retrieval recovered through a degraded fallback path","type":"boolean"},"degraded_reason":{"description":"Coarse public reason for degraded retrieval, when degraded is true","enum":["stage1_exhausted","query_vector_unavailable"],"nullable":true,"type":"string"},"retrieval_mode":{"description":"Whether the answer used the normal retrieval path or a degraded fallback path","enum":["normal","degraded"],"type":"string"},"sources_truncated":{"description":"True when retrieval evidence exceeded the response bounds (64 sources, 2,000 characters each, or 24,000 characters total)","type":"boolean"},"temporal_fallback_triggered":{"description":"Retrieval retried without the requested temporal constraint because the constrained search had no usable evidence","type":"boolean"}},"required":["retrieval_mode","degraded"],"title":"LibraryAiRetrievalMetadata","type":"object"},"CreateAuthSessionRequest":{"properties":{"client_ip":{"description":"Optional end-user IP address used only for the session's coarse geo-location (city/country); the raw IP is never returned. Intended for trusted server-side integrators whose connecting IP is a datacenter egress (e.g. a Next.js app on Vercel): forward the real viewer IP here so the stored location reflects the viewer, not your server. When omitted, the server uses the proxy-aware connecting IP. Accepts IPv4 or IPv6; an unparseable value is ignored and the connecting IP is used.","nullable":true,"type":"string"},"device_id":{"type":"string"},"device_label":{"description":"Optional human-readable device label. When omitted (the usual case), the server derives one from `user_agent` (e.g. \"Chrome · macOS\"). A non-blank value supplied here takes precedence and is stored as-is (max 255 chars).","nullable":true,"type":"string"},"platform":{"enum":["web","ios","android"],"type":"string"},"user_agent":{"nullable":true,"type":"string"}},"required":["device_id","platform"],"title":"CreateAuthSessionRequest","type":"object"},"CommunityPostCreate":{"description":"Parameters for creating a community post","example":{"post":{"category":"announcements","content":"Welcome to our community!","mentions":["outseta:AbC123"]}},"properties":{"post":{"description":"Post parameters","properties":{"category":{"description":"Post category (optional, defaults to 'general')","enum":["announcements","q_and_a","general"],"example":"announcements","type":"string"},"content":{"description":"Post content (required)","example":"Welcome to our community!","type":"string"},"mentions":{"$ref":"#/components/schemas/MentionsParam"}},"required":["content"],"type":"object"}},"required":["post"],"title":"CommunityPostCreate","type":"object"},"MetricsEventRequest":{"anyOf":[{"$ref":"#/components/schemas/AudienceEventRequest"},{"additionalProperties":true,"description":"Legacy SDK analytics payload; existing legacy fields are unchanged.","type":"object"}],"description":"Events with n=source_open, or n=video_progress containing interaction_id/playback_id, use the dedicated audience path. Other event names retain the legacy contract even with extra ID fields.","title":"MetricsEventRequest"},"SessionManagementRevokeAllResponse":{"properties":{"ok":{"type":"boolean"},"revoked_count":{"type":"integer"}},"required":["ok","revoked_count"],"title":"SessionManagementRevokeAllResponse","type":"object"},"CommunityPost":{"description":"A community post","properties":{"author":{"$ref":"#/components/schemas/CommunityAuthor"},"category":{"description":"Post category","enum":["announcements","q_and_a","general"],"example":"announcements","type":"string"},"comments":{"description":"Comments summary","properties":{"commented_by":{"description":"Users who commented (unique, capped at 10)","items":{"$ref":"#/components/schemas/CommunityAuthor"},"maxItems":10,"type":"array"},"count":{"description":"Total number of comments","example":3,"type":"integer"},"items":{"description":"Full comment threads (only in detail endpoint)","items":{"$ref":"#/components/schemas/CommunityComment"},"type":"array"}},"required":["count","commented_by"],"type":"object"},"content":{"description":"Post content","example":"Welcome to our community!","type":"string"},"created_at":{"description":"Creation timestamp","format":"date-time","type":"string"},"edited_at":{"description":"Last content edit; pin changes and import repairs do not set this","format":"date-time","nullable":true,"type":"string"},"id":{"description":"Post UUID","format":"uuid","type":"string"},"pinned":{"description":"Whether the post is pinned","example":false,"type":"boolean"},"pinned_at":{"description":"When the post was pinned","format":"date-time","nullable":true,"type":"string"},"reactions":{"description":"Reactions summary","properties":{"count":{"description":"Total number of reactions","example":10,"type":"integer"},"reacted_by":{"description":"Users who reacted (most recent, capped at 10)","items":{"$ref":"#/components/schemas/CommunityAuthor"},"maxItems":10,"type":"array"},"viewer_has_reacted":{"description":"Whether the current viewer has reacted (only when X-Viewer-ID header is provided)","nullable":true,"type":"boolean"}},"required":["count","reacted_by"],"type":"object"},"updated_at":{"description":"Last update timestamp","format":"date-time","type":"string"}},"required":["id","content","category","pinned","author","reactions","comments","created_at"],"title":"CommunityPost","type":"object"},"ViewerMemoryUpdate":{"description":"Parameters for updating a viewer's memory settings.","properties":{"memory":{"description":"Memory settings to update.","properties":{"enabled":{"description":"Set false to opt the viewer out of re-distillation; true to opt back in.","example":false,"type":"boolean"}},"required":["enabled"],"type":"object"}},"required":["memory"],"title":"ViewerMemoryUpdate","type":"object"},"ViewersResponse":{"description":"Response containing a paginated list of viewers","properties":{"data":{"description":"List of viewers","items":{"$ref":"#/components/schemas/Viewer"},"type":"array"},"meta":{"description":"Pagination metadata","properties":{"page":{"description":"Current page number (1-indexed)","example":1,"type":"integer"},"page_size":{"description":"Items per page","example":20,"type":"integer"},"total_entries":{"description":"Total number of items across all pages","example":47,"type":"integer"},"total_pages":{"description":"Total number of pages","example":3,"type":"integer"}},"required":["page","page_size","total_entries","total_pages"],"type":"object"}},"required":["data","meta"],"title":"ViewersResponse","type":"object"},"Citation":{"description":"A source reference from video content.\n\nThe `cited` field indicates whether this source is referenced in the answer text.\nCitation indices in the answer (e.g., `[4]`) map to array positions: `[4]` → `sources[3]`.\n\n**Field naming**: Use the canonical field names (`title`, `timestamp`, `timestamp_end`, `playback_id`).\nDeprecated aliases (`video_title`, `timestamp_seconds`, `timestamp_end_seconds`, `mux_playback_id`)\nare included for backward compatibility and will be removed in a future release.\n","properties":{"attachment_filename":{"description":"Attachment filename when document_type is attachment","nullable":true,"type":"string"},"attachment_id":{"description":"Attachment ID when document_type is attachment","nullable":true,"type":"integer"},"canonical_url":{"description":"Canonical video URL for click-through","nullable":true,"type":"string"},"cited":{"description":"Whether this source is cited in the answer text. Filter by `cited: true` to show only referenced sources.","type":"boolean"},"document_type":{"description":"Document type (transcript or attachment)","type":"string"},"id":{"description":"Citation ID","type":"string"},"kind":{"description":"Discriminates citation provenance:\n- `\"chunk\"` (default) — transcript chunk, resource, or attachment\n- `\"moment\"` — LLM-extracted curated moment (insight / story / framework / q_and_a / exercise)\nWhen `\"moment\"`, the `moment_id`, `type`, `summary`, and `transcript_excerpt`\nfields are populated.\n","enum":["chunk","moment"],"type":"string"},"matched_sentence":{"description":"The transcript sentence whose text best matches the cited claim, when a\nconfident match was found. When present, the citation's `timestamp` /\n`timestamp_end` are narrowed to this sentence instead of the coarse chunk\nbounds. The key is **omitted entirely** (not null) for legacy chunks or\nlow-confidence matches, so the citation falls back to chunk-level timing.\n","nullable":true,"properties":{"end_ms":{"description":"Sentence end (ms)","type":"integer"},"speaker_label":{"description":"Raw diarization label for this sentence, or null.","nullable":true,"type":"string"},"speaker_name":{"description":"Resolved speaker name for this sentence, or null.","nullable":true,"type":"string"},"start_ms":{"description":"Sentence start (ms)","type":"integer"},"text":{"description":"Sentence text","type":"string"},"timestamp":{"description":"Sentence start in seconds (canonical)","type":"integer"},"timestamp_end":{"description":"Sentence end in seconds (canonical)","type":"integer"},"timestamp_end_seconds":{"deprecated":true,"description":"DEPRECATED: Use `timestamp_end` instead","type":"integer"},"timestamp_seconds":{"deprecated":true,"description":"DEPRECATED: Use `timestamp` instead","type":"integer"}},"type":"object"},"moment_id":{"description":"Source moment UUID (present only when `kind=\"moment\"`)","nullable":true,"type":"string"},"mux_playback_id":{"deprecated":true,"description":"DEPRECATED: Use `playback_id` instead","type":"string"},"playback_id":{"description":"Mux playback ID; null for non-playback sources","nullable":true,"type":"string"},"playback_token":{"description":"Fresh Mux playback JWT for signed videos; null for public or non-playback sources.","nullable":true,"type":"string"},"published_at":{"description":"Publication timestamp (Unix epoch seconds)","nullable":true,"type":"integer"},"slug":{"description":"Video slug when available","nullable":true,"type":"string"},"speaker":{"description":"Resolved speaker name when identified, otherwise the raw diarization label (back-compat). Prefer `speaker_name`.","nullable":true,"type":"string"},"speaker_label":{"description":"Raw diarization label (e.g. \"A\"), or null.","nullable":true,"type":"string"},"speaker_name":{"description":"Resolved speaker display name, or null when the speaker is unnamed or the chunk predates speaker resolution.","nullable":true,"type":"string"},"summary":{"description":"Curated LLM-extracted summary of the moment (present only when `kind=\"moment\"`). Frontend may show this as the primary label for the citation pill.","nullable":true,"type":"string"},"text":{"description":"Quoted text","type":"string"},"timestamp":{"description":"Start time in seconds; null for untimed sources such as attachments","nullable":true,"type":"integer"},"timestamp_end":{"description":"End time in seconds; null for untimed sources such as attachments","nullable":true,"type":"integer"},"timestamp_end_seconds":{"deprecated":true,"description":"DEPRECATED: Use `timestamp_end` instead","type":"integer"},"timestamp_seconds":{"deprecated":true,"description":"DEPRECATED: Use `timestamp` instead","type":"integer"},"timestamp_url":{"description":"Canonical URL with `?t=<seconds>` when timestamp is available","nullable":true,"type":"string"},"title":{"description":"Video title","type":"string"},"transcript_excerpt":{"description":"Verbatim transcript text covered by the moment (present only when `kind=\"moment\"`). Mirrors `text` for clients that don't discriminate by `kind`.","nullable":true,"type":"string"},"type":{"description":"Moment type (present only when `kind=\"moment\"`)","enum":["insight","story","framework","q_and_a","exercise"],"nullable":true,"type":"string"},"video_id":{"description":"Source video ID","type":"string"},"video_title":{"deprecated":true,"description":"DEPRECATED: Use `title` instead","type":"string"}},"required":["id","video_id","title","text","timestamp","cited"],"title":"Citation","type":"object"},"LibraryAiChatRequest":{"description":"Chat request across your entire video library","example":{"prompt":"What are the best practices for user authentication?","stream":false,"synthesize":true},"properties":{"channel":{"description":"Optional caller channel for analytics. Missing or unrecognized values are recorded as `unknown` and never reject the AI request.","enum":["portal","embed","api","mcp"],"type":"string"},"client_name":{"description":"Optional analytics client name. Trimmed values must be 1–128 characters and at most 512 UTF-8 bytes; invalid values become unknown and never reject the request.","maxLength":128,"minLength":1,"type":"string"},"client_version":{"description":"Optional analytics client version. Trimmed values must be 1–128 characters and at most 512 UTF-8 bytes; invalid values become unknown and never reject the request.","maxLength":128,"minLength":1,"type":"string"},"collection_id":{"description":"Limit search to a specific collection (legacy single-value filter)","nullable":true,"type":"string"},"collection_ids":{"description":"Limit search to multiple collections (OR semantics)","items":{"type":"string"},"nullable":true,"type":"array"},"conversation_id":{"description":"Continue an existing conversation","nullable":true,"type":"string"},"images":{"description":"Optional array of images (max 5). Requires the Vision feature flag. Each image can be base64-encoded or a URL.","items":{"anyOf":[{"$ref":"#/components/schemas/AiImageBase64"},{"$ref":"#/components/schemas/AiImageUrl"}]},"maxItems":5,"nullable":true,"type":"array"},"prompt":{"description":"Your question","example":"What videos cover authentication?","type":"string"},"request_id":{"description":"Caller action UUID. Reuse unchanged on retries to deduplicate analytics, not model execution. Omission or an invalid UUID creates a new interaction.","format":"uuid","type":"string"},"stream":{"default":true,"description":"Stream response via SSE (default: true)","type":"boolean"},"synthesize":{"default":true,"description":"Set false to stop after scoped two-tier retrieval and return ordered evidence without answer synthesis. JSON/form strings `true` and `false` are also accepted.","type":"boolean"},"tags":{"description":"Filter by video tags","items":{"type":"string"},"nullable":true,"type":"array"},"viewer":{"description":"Viewer identifier (UUID or your external ID) to associate with the conversation. When the viewers feature is enabled, the viewer's traits also personalize AI responses.","nullable":true,"type":"string"},"viewer_profile":{"anyOf":[{"description":"Free text or markdown describing the viewer; replaces the synced viewer's traits for this request. Up to 8000 characters.","example":"Sarah runs a 3-person agency, pre-launch, wants her first 10 customers.","type":"string"},{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"Key-value traits merged over the synced viewer's traits (inline wins per key). Max 20 keys; keys must match ^[a-zA-Z_][a-zA-Z0-9_]*$; values are strings (up to 2000 characters), numbers, or booleans.","example":{"business_name":"Acme Corp","goal":"find first customers","stage":"growth"},"type":"object"}],"description":"Tell the AI about the person asking, for this request only (never persisted). Pass free text/markdown, or a key-value object. Requires the viewers feature.","nullable":true}},"required":["prompt"],"title":"LibraryAiChatRequest","type":"object"},"NotificationPreferencesResponse":{"description":"A viewer's notification channel preferences.","properties":{"channels":{"$ref":"#/components/schemas/NotificationChannels"}},"required":["channels"],"title":"NotificationPreferencesResponse","type":"object"},"ViewerResponse":{"description":"Response containing a single viewer","properties":{"data":{"$ref":"#/components/schemas/Viewer"}},"required":["data"],"title":"ViewerResponse","type":"object"},"AdminCollectionResponse":{"properties":{"data":{"allOf":[{"$ref":"#/components/schemas/Collection"}],"description":"Collection JSON; video_count includes all memberships on admin routes."},"existing":{"description":"True when the exact name already existed","type":"boolean"}},"required":["data","existing"],"title":"AdminCollectionResponse","type":"object"},"AdminRevokeAllResponse":{"properties":{"ok":{"type":"boolean"},"revoked_count":{"type":"integer"}},"required":["ok","revoked_count"],"title":"AdminRevokeAllResponse","type":"object"},"SelfManagementDisabledError":{"properties":{"error":{"enum":["self_management_disabled"],"type":"string"}},"required":["error"],"title":"SelfManagementDisabledError","type":"object"},"SessionManagementSessionWithRevocation":{"properties":{"device_id":{"type":"string"},"device_label":{"description":"Server-derived human-readable label for the device/browser and OS (e.g. \"Chrome · macOS\", \"Safari · iPhone\"). Derived from the `user_agent` supplied at session creation. Nullable when the user_agent is absent or unrecognised; callers should fall back to `device_id` for display.","nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"last_seen_at":{"format":"date-time","type":"string"},"location":{"description":"Coarse, pre-formatted client location (e.g. \"Berlin, DE\" or \"DE\"). Never the raw IP.","nullable":true,"type":"string"},"platform":{"type":"string"},"revoked_at":{"format":"date-time","nullable":true,"type":"string"},"revoked_by":{"nullable":true,"type":"string"},"revoked_reason":{"nullable":true,"type":"string"},"travel_verdict":{"description":"Impossible-travel verdict label. \"impossible_travel\" when the sign-in was flagged as physically implausible, otherwise null. Label only — never exposes coordinates, the raw IP, or derived distance/speed metrics.","nullable":true,"type":"string"}},"title":"SessionManagementSessionWithRevocation","type":"object"},"AdminRevokeAuthSessionResponse":{"properties":{"ok":{"type":"boolean"},"session_id":{"format":"uuid","type":"string"}},"required":["ok","session_id"],"title":"AdminRevokeAuthSessionResponse","type":"object"},"MultimodalSettings":{"description":"Per-account capability flag for multimodal (image input) on AI endpoints.\n\nWhen `enabled` is `true`, clients may attach the `images` array on requests to\n`/api/v1/ai/chat`, `/api/v1/ai/search`, and `/api/v1/ai/videos/:id/chat`\n(`/api/v1/ai/ask` remains as a deprecated alias for `/api/v1/ai/chat`). When\n`false`, those requests are rejected with `feature_disabled`.\n\nLimits (`max_images`, `accepted_media_types`) are only included when enabled.\n","properties":{"accepted_media_types":{"description":"Accepted base64 media types (only present when enabled)","example":["image/png","image/jpeg","image/webp","image/gif"],"items":{"type":"string"},"type":"array"},"enabled":{"description":"Whether image inputs are accepted on AI endpoints","example":false,"type":"boolean"},"max_images":{"description":"Maximum number of images per request (only present when enabled)","example":5,"type":"integer"}},"required":["enabled"],"title":"MultimodalSettings","type":"object"},"VideoProgress":{"description":"A viewer's watch progress on a video","example":{"completed":false,"completed_at":null,"current_time":300,"duration":1000,"id":"550e8400-e29b-41d4-a716-446655440000","percentage":30.0},"nullable":true,"properties":{"completed":{"description":"Whether the viewer has completed watching","example":false,"type":"boolean"},"completed_at":{"description":"Timestamp when video was marked complete","format":"date-time","nullable":true,"type":"string"},"current_time":{"description":"Current playback position in seconds","example":300,"type":"integer"},"duration":{"description":"Total video duration in seconds","example":1000,"type":"integer"},"id":{"description":"Progress record ID","format":"uuid","type":"string"},"percentage":{"description":"Watch progress percentage (0-100)","example":30.0,"format":"float","type":"number"}},"title":"VideoProgress","type":"object"},"AdminCollectionRequest":{"properties":{"conversation_starters":{"description":"Newline-separated prompts; null/blank inherits account defaults. Write-only.","maxLength":2000,"nullable":true,"type":"string"},"name":{"maxLength":100,"minLength":1,"type":"string"},"parent_id":{"format":"uuid","nullable":true,"type":"string"},"position":{"minimum":0,"type":"integer"}},"title":"AdminCollectionRequest","type":"object"},"CollectionsResponse":{"description":"Response containing a list of collections","properties":{"data":{"description":"List of collections","items":{"$ref":"#/components/schemas/Collection"},"type":"array"}},"required":["data"],"title":"CollectionsResponse","type":"object"},"LibraryConversationListResponse":{"properties":{"conversations":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/ConversationPagination"}},"required":["conversations","pagination"],"title":"LibraryConversationListResponse","type":"object"},"ViewerCreate":{"description":"Parameters for creating a viewer","properties":{"avatar_url":{"description":"URL to avatar image","example":"https://example.com/avatars/john.jpg","format":"uri","nullable":true,"type":"string"},"display_name":{"description":"Preferred display name","example":"Johnny D","nullable":true,"type":"string"},"email":{"description":"Viewer email","example":"john@example.com","format":"email","type":"string"},"external_id":{"description":"External ID from course platform","example":"usr_123abc","nullable":true,"type":"string"},"first_name":{"description":"First name","example":"John","nullable":true,"type":"string"},"last_name":{"description":"Last name","example":"Doe","nullable":true,"type":"string"},"name":{"description":"Viewer name","example":"John Doe","type":"string"},"traits":{"description":"Custom traits map","example":{"business_name":"Acme Corp","stage":"growth"},"nullable":true,"type":"object"}},"required":["name"],"title":"ViewerCreate","type":"object"},"LibraryAiStreamSource":{"description":"A pre-synthesis source emitted by the existing `sources` SSE event. Unlike terminal answer citations and retrieval evidence, its ID may be null.","properties":{"id":{"nullable":true,"type":"string"},"playback_id":{"nullable":true,"type":"string"},"playback_token":{"description":"Fresh signed playback JWT; null for public or non-playback sources.","nullable":true,"type":"string"},"resource_id":{"format":"uuid","nullable":true,"type":"string"},"speaker":{"nullable":true,"type":"string"},"speaker_label":{"nullable":true,"type":"string"},"speaker_name":{"nullable":true,"type":"string"},"text":{"type":"string"},"timestamp":{"type":"integer"},"timestamp_end":{"type":"integer"},"title":{"type":"string"},"video_id":{"nullable":true,"type":"string"}},"required":["id","video_id","title","text","timestamp","timestamp_end","playback_id"],"title":"LibraryAiStreamSource","type":"object"},"AdminVideoIngestRequest":{"properties":{"collection_id":{"description":"Single collection alias; combined with collection_ids if both are supplied","format":"uuid","type":"string"},"collection_ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"description":{"type":"string"},"external_id":{"type":"string"},"language":{"default":"en","type":"string"},"privacy":{"default":"unlisted","enum":["public","unlisted","private"],"type":"string"},"published_at":{"description":"ISO 8601 with timezone; normalized to UTC seconds. Omitted/null means unpublished.","format":"date-time","nullable":true,"type":"string"},"source_url":{"description":"One direct media or YouTube/Vimeo video URL; not a playlist/channel","format":"uri","type":"string"},"tags":{"description":"Explicit tag names only; video-type defaults are not added","items":{"type":"string"},"type":"array"},"title":{"description":"Required for direct media; otherwise defaults to the importer title","type":"string"},"video_type":{"description":"System or account-owned video type UUID or slug; account slug takes precedence. Stored as active_video_type_id.","type":"string"}},"required":["source_url"],"title":"AdminVideoIngestRequest","type":"object"},"AdminDeviceLimitResponse":{"properties":{"data":{"properties":{"device_limit_override":{"nullable":true,"type":"integer"},"viewer_id":{"format":"uuid","type":"string"}},"type":"object"}},"required":["data"],"title":"AdminDeviceLimitResponse","type":"object"},"SessionManagementDeviceLimitRequest":{"properties":{"limit":{"description":"Set a positive integer override, or null to clear it.","minimum":1,"nullable":true,"type":"integer"}},"required":["limit"],"title":"SessionManagementDeviceLimitRequest","type":"object"},"AdminImportRequest":{"properties":{"collection_ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"limit":{"description":"Maximum expanded videos; omitted imports all","minimum":1,"type":"integer"},"order":{"default":"latest","enum":["latest","oldest"],"type":"string"},"privacy":{"default":"unlisted","enum":["public","unlisted","private"],"type":"string"},"published_at_strategy":{"default":"source","description":"source uses YouTube publish date (now if unavailable); none leaves published_at null","enum":["source","now","none"],"type":"string"},"source_url":{"description":"YouTube playlist, channel or single video URL","format":"uri","type":"string"},"tags":{"items":{"type":"string"},"type":"array"},"video_type":{"description":"System or account video type UUID; omitted uses account default","format":"uuid","type":"string"}},"required":["source_url"],"title":"AdminImportRequest","type":"object"},"AdminCommunityCommentUpdate":{"description":"Parameters for repairing an imported comment: its `content` and either\ntimestamp. The author, the thread the comment belongs to, the counters, and\n`external_ref` cannot be changed through this route. Comments have no\n`category`.\n\nOmitted fields keep their stored values, except that a request without\n`updated_at` stamps it to now — send both when repairing the dates of a\ncomment that was never really edited.\n","example":{"comment":{"content":"Welcome aboard! (fixed the broken link)","created_at":"2024-03-02T09:30:00Z","updated_at":"2024-03-02T09:30:00Z"}},"properties":{"comment":{"description":"Comment parameters","properties":{"content":{"description":"Corrected comment content (required)","example":"Welcome aboard! (fixed the broken link)","maxLength":5000,"minLength":1,"type":"string"},"created_at":{"description":"Corrected posting time, ISO-8601. Truncated to seconds. Moves the comment within its thread. A time in the future is rejected (422), as is one that lands after the stored `updated_at` — send `updated_at` too in that case.","example":"2024-03-02T09:30:00Z","format":"date-time","type":"string"},"updated_at":{"description":"Corrected edit time, ISO-8601. A time in the future, or one before `created_at`, is rejected (422).","example":"2024-03-02T10:00:00Z","format":"date-time","type":"string"}},"required":["content"],"type":"object"}},"required":["comment"],"title":"AdminCommunityCommentUpdate","type":"object"},"ImageUploadRequest":{"description":"Multipart image upload","properties":{"file":{"description":"Image file bytes. Sniffed by magic bytes against jpeg/png/gif/webp/avif (the client-declared content type is never trusted); SVG is rejected. Max 5 MB.","format":"binary","type":"string"}},"required":["file"],"title":"ImageUploadRequest","type":"object"},"AdminImportResponse":{"properties":{"data":{"$ref":"#/components/schemas/AdminImportRun"}},"required":["data"],"title":"AdminImportResponse","type":"object"},"ConversationStartersResponse":{"description":"Response containing the resolved conversation-starter pool","properties":{"data":{"description":"List of resolved conversation starters (order is deterministic but not meaningful)","items":{"$ref":"#/components/schemas/ConversationStarter"},"type":"array"}},"required":["data"],"title":"ConversationStartersResponse","type":"object"},"SessionManagementViewerStateResponse":{"properties":{"data":{"$ref":"#/components/schemas/SessionManagementViewerState"}},"required":["data"],"title":"SessionManagementViewerStateResponse","type":"object"},"Progress":{"description":"A viewer's progress on a video","example":{"completed":false,"completed_at":null,"created_at":"2026-01-07T12:00:00Z","current_time":300,"duration":1000,"id":"98936fa0-6826-48cc-aac1-89f42235dc8e","percentage":30.0,"updated_at":"2026-01-07T12:05:00Z","video_id":"87654321-4321-4321-4321-987654321cba","viewer_id":"12345678-1234-1234-1234-123456789abc"},"properties":{"completed":{"description":"Whether the video has been completed","type":"boolean"},"completed_at":{"description":"Timestamp when video was completed","format":"date-time","nullable":true,"type":"string"},"created_at":{"description":"Record creation timestamp","format":"date-time","type":"string"},"current_time":{"description":"Current playback position in seconds","minimum":0,"type":"integer"},"duration":{"description":"Total video duration in seconds","minimum":1,"type":"integer"},"id":{"description":"Progress record UUID","format":"uuid","type":"string"},"percentage":{"description":"Completion percentage (0-100)","format":"decimal","type":"number"},"updated_at":{"description":"Record last updated timestamp","format":"date-time","type":"string"},"video_id":{"description":"Video UUID","format":"uuid","type":"string"},"viewer_id":{"description":"Viewer UUID","format":"uuid","type":"string"}},"required":["id","viewer_id","video_id","current_time","duration","percentage","completed"],"title":"Progress","type":"object"},"NotificationPreferencesUpdate":{"description":"Notification channels to update. Provide one or both.","properties":{"channels":{"description":"Channel switches to update.","properties":{"email":{"description":"Set false to stop email notifications; true to resume.","example":false,"type":"boolean"},"push":{"description":"Set false to stop push notifications; true to resume.","example":true,"type":"boolean"}},"type":"object"}},"required":["channels"],"title":"NotificationPreferencesUpdate","type":"object"},"VideosResponse":{"description":"Response containing a list of videos","properties":{"data":{"description":"List of videos","items":{"$ref":"#/components/schemas/Video"},"type":"array"}},"required":["data"],"title":"VideosResponse","type":"object"},"ProgressResponse":{"description":"Response containing a single progress record","example":{"data":{"completed":false,"completed_at":null,"created_at":"2026-01-07T12:00:00Z","current_time":300,"duration":1000,"id":"98936fa0-6826-48cc-aac1-89f42235dc8e","percentage":30.0,"updated_at":"2026-01-07T12:05:00Z","video_id":"87654321-4321-4321-4321-987654321cba","viewer_id":"12345678-1234-1234-1234-123456789abc"}},"properties":{"data":{"$ref":"#/components/schemas/Progress"}},"required":["data"],"title":"ProgressResponse","type":"object"},"PlaylistsResponse":{"description":"List of playlists","properties":{"data":{"description":"Playlists","items":{"$ref":"#/components/schemas/Playlist"},"type":"array"}},"required":["data"],"title":"PlaylistsResponse","type":"object"},"ReactionResponse":{"description":"Response after toggling a reaction on a post or comment","example":{"data":{"reacted":true,"reactions_count":5}},"properties":{"data":{"description":"Reaction result","properties":{"reacted":{"description":"Whether the viewer currently has an active reaction","example":true,"type":"boolean"},"reactions_count":{"description":"Updated total number of reactions","example":5,"type":"integer"}},"required":["reacted","reactions_count"],"type":"object"}},"required":["data"],"title":"ReactionResponse","type":"object"},"ConversationSummary":{"properties":{"conversation_id":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"message_count":{"type":"integer"},"preview":{"nullable":true,"type":"string"},"scope":{"description":"Conversation scope: video (video-scoped chat) or library (library-wide chat)","enum":["video","library"],"nullable":true,"type":"string"},"status":{"type":"string"},"title":{"description":"Conversation title (auto-generated)","nullable":true,"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"required":["conversation_id","status","message_count","created_at","updated_at"],"title":"ConversationSummary","type":"object"},"Viewer":{"description":"An external viewer (user from course platform)","properties":{"avatar_url":{"description":"URL to avatar image","example":"https://example.com/avatars/john.jpg","format":"uri","nullable":true,"type":"string"},"computed_display_name":{"description":"Computed display name (priority: display_name > first+last name > first name > name > 'Anonymous')","example":"John Doe","type":"string"},"created_at":{"description":"Creation timestamp","format":"date-time","type":"string"},"display_name":{"description":"Preferred display name","example":"Johnny D","nullable":true,"type":"string"},"email":{"description":"Viewer email","example":"john@example.com","format":"email","type":"string"},"external_id":{"description":"External ID from course platform","example":"usr_123abc","nullable":true,"type":"string"},"first_name":{"description":"First name","example":"John","nullable":true,"type":"string"},"id":{"description":"Viewer UUID","format":"uuid","type":"string"},"is_admin":{"description":"Whether this viewer is a community administrator (read-only via API)","example":false,"type":"boolean"},"last_name":{"description":"Last name","example":"Doe","nullable":true,"type":"string"},"name":{"description":"Viewer name","example":"John Doe","type":"string"},"traits":{"description":"Custom traits map","example":{"business_name":"Acme Corp","stage":"growth"},"nullable":true,"type":"object"},"updated_at":{"description":"Last update timestamp","format":"date-time","type":"string"}},"required":["id","name","computed_display_name"],"title":"Viewer","type":"object"},"AdminAuthPolicyResponse":{"properties":{"admin_override_per_user":{"type":"boolean"},"allow_member_self_manage":{"type":"boolean"},"auth_challenge_product_name":{"nullable":true,"type":"string"},"auth_challenge_reply_to_email":{"format":"email","nullable":true,"type":"string"},"auth_challenge_sender_name":{"nullable":true,"type":"string"},"auth_challenge_support_url":{"format":"uri","nullable":true,"type":"string"},"auth_challenge_template_id":{"nullable":true,"type":"string"},"enforcement_mode":{"enum":["block_new","kick_oldest","challenge_new"],"type":"string"},"enforcement_phase":{"enum":["off","shadow","enforce"],"type":"string"},"impossible_travel_mode":{"description":"Impossible travel detection mode. off disables detection.","enum":["off","flag_only","challenge"],"type":"string"},"impossible_travel_speed_kmh":{"description":"Speed threshold in km/h above which a login is flagged as impossible travel.","minimum":1,"type":"integer"},"inserted_at":{"format":"date-time","type":"string"},"max_devices_per_user":{"type":"integer"},"session_ttl_days":{"type":"integer"},"updated_at":{"format":"date-time","type":"string"}},"title":"AdminAuthPolicyResponse","type":"object"},"CommunityCommentResponse":{"description":"Response containing a single comment","properties":{"data":{"$ref":"#/components/schemas/CommunityComment"},"mentions":{"$ref":"#/components/schemas/MentionsSkipped"}},"required":["data"],"title":"CommunityCommentResponse","type":"object"},"ThemeConfig":{"description":"Theme configuration with light and dark mode tokens","properties":{"color_scheme":{"description":"Color scheme preference","enum":["light","dark","toggle"],"type":"string"},"dark":{"$ref":"#/components/schemas/ThemeTokens"},"light":{"$ref":"#/components/schemas/ThemeTokens"},"radius":{"description":"Border radius","example":"0.625rem","type":"string"}},"title":"ThemeConfig","type":"object"},"AdminVideosResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminVideo"},"type":"array"},"meta":{"description":"More results exist when page < total_pages; follow the Link header's next relation or request page + 1 with the same filters and page_size.","properties":{"page":{"type":"integer"},"page_size":{"type":"integer"},"total_entries":{"type":"integer"},"total_pages":{"type":"integer"}},"required":["page","page_size","total_entries","total_pages"],"type":"object"}},"required":["data","meta"],"title":"AdminVideosResponse","type":"object"},"VideoResponse":{"description":"Response containing a single video","properties":{"data":{"$ref":"#/components/schemas/Video"}},"required":["data"],"title":"VideoResponse","type":"object"},"RevokeOthersResponse":{"properties":{"ok":{"type":"boolean"},"revoked_count":{"type":"integer"}},"required":["ok","revoked_count"],"title":"RevokeOthersResponse","type":"object"},"VoiceSettings":{"description":"Read-only voice capability for rendering client controls. Every new voice\nsession independently checks the account's AI and Voice flags and its voice\nswitch in AI Settings. Availability\ndoes not guarantee a session can start: access, transcript, usage limits, and\nprovider configuration are checked when creating the session.\n","properties":{"enabled":{"description":"Whether AI and Voice access and the account's voice setting are enabled","example":false,"readOnly":true,"type":"boolean"}},"required":["enabled"],"title":"VoiceSettings","type":"object"},"AiRecommendationsRequest":{"description":"Request for AI-powered video recommendations","example":{"limit":5,"topics":["authentication","user management"]},"properties":{"collection_id":{"description":"Filter to a specific collection (legacy single-value filter)","nullable":true,"type":"string"},"collection_ids":{"description":"Filter to multiple collections (OR semantics)","items":{"type":"string"},"nullable":true,"type":"array"},"include_guidance":{"default":true,"description":"Include AI guidance text explaining recommendations","type":"boolean"},"limit":{"description":"Maximum recommendations (default: 5, max: 20)","example":5,"type":"integer"},"stream":{"default":true,"description":"Stream response via SSE (default: true)","type":"boolean"},"tags":{"description":"Filter by video tags","items":{"type":"string"},"nullable":true,"type":"array"},"topics":{"description":"Topics or learning goals","example":["authentication","getting started"],"items":{"type":"string"},"type":"array"}},"required":["topics"],"title":"AiRecommendationsRequest","type":"object"},"VerifyAuthSessionResponse":{"properties":{"reason":{"enum":["session_not_found","session_revoked","session_expired"],"nullable":true,"type":"string"},"valid":{"type":"boolean"}},"required":["valid"],"title":"VerifyAuthSessionResponse","type":"object"},"ProgressListResponse":{"description":"Response containing a list of progress records with metadata","example":{"data":[{"completed":false,"completed_at":null,"created_at":"2026-01-07T12:00:00Z","current_time":300,"duration":1000,"id":"98936fa0-6826-48cc-aac1-89f42235dc8e","percentage":30.0,"updated_at":"2026-01-07T12:05:00Z","video_id":"87654321-4321-4321-4321-987654321cba","viewer_id":"12345678-1234-1234-1234-123456789abc"}],"meta":{"completed":3,"in_progress":7,"total":10}},"properties":{"data":{"description":"List of progress records","items":{"$ref":"#/components/schemas/Progress"},"type":"array"},"meta":{"properties":{"completed":{"description":"Number of completed videos","type":"integer"},"in_progress":{"description":"Number of videos in progress","type":"integer"},"total":{"description":"Total number of progress records","type":"integer"}},"type":"object"}},"required":["data","meta"],"title":"ProgressListResponse","type":"object"},"SessionManagementViewerResolveResponse":{"properties":{"data":{"$ref":"#/components/schemas/SessionManagementViewerState"}},"required":["data"],"title":"SessionManagementViewerResolveResponse","type":"object"},"HeroSettings":{"description":"Hero section configuration","properties":{"type":{"description":"Hero section type","enum":["none","custom"],"example":"none","type":"string"}},"required":["type"],"title":"HeroSettings","type":"object"},"ConversationResponse":{"description":"Full conversation history","properties":{"conversation_id":{"description":"Conversation ID","type":"string"},"created_at":{"format":"date-time","type":"string"},"messages":{"description":"Message history","items":{"properties":{"content":{"type":"string"},"interaction_id":{"description":"Persisted interaction linked to this exact assistant message. Null for other roles or when no unique eligible capture exists, including pending capture, legacy messages, and erased interactions.","format":"uuid","nullable":true,"type":"string"},"role":{"enum":["user","assistant"],"type":"string"}},"type":"object"},"type":"array"},"updated_at":{"format":"date-time","type":"string"}},"required":["conversation_id","messages"],"title":"ConversationResponse","type":"object"},"ApiAuthErrorResponse":{"description":"Authentication failure response returned by API token plugs.","properties":{"error":{"example":"Unauthorized","type":"string"},"status":{"example":401,"type":"integer"},"timestamp":{"example":"2026-04-25 04:00:00Z","type":"string"}},"required":["error","status","timestamp"],"title":"ApiAuthErrorResponse","type":"object"},"VoiceSessionResponse":{"description":"Apply `sdp` as the peer connection's remote description. The client ends the call after `max_seconds`, or after `idle_seconds` of silence.","properties":{"idle_seconds":{"description":"End the session after this many idle seconds","example":60,"type":"integer"},"max_seconds":{"description":"Maximum session length in seconds","example":600,"type":"integer"},"sdp":{"description":"The WebRTC SDP answer","type":"string"},"session_id":{"description":"Opaque voice session ID","type":"string"}},"required":["session_id","sdp","max_seconds","idle_seconds"],"title":"VoiceSessionResponse","type":"object"},"CustomRedirect":{"description":"A branded shortlink redirect","properties":{"path":{"description":"URL path (e.g., /apple)","example":"/apple","type":"string"},"permanent":{"description":"Whether redirect is permanent (301 vs 307)","example":false,"type":"boolean"},"url":{"description":"Target URL","example":"https://podcasts.apple.com/...","type":"string"}},"required":["path","url","permanent"],"title":"CustomRedirect","type":"object"},"AdminCommunityPostUpdate":{"description":"Parameters for repairing an imported post: its `content`, `category`, and\neither timestamp. The author, the counters, and `external_ref` cannot be\nchanged through this route, even if present in the request body.\n\nOmitted fields keep their stored values — with one exception: as on any\nedit, a request that does not carry `updated_at` stamps it to now. When\nrepairing the dates of a row that was never really edited, send both.\n","example":{"post":{"category":"announcements","content":"Kicking off week one — introduce yourself! (fixed image links)","created_at":"2024-03-01T12:00:00Z","updated_at":"2024-03-01T12:00:00Z"}},"properties":{"post":{"description":"Post parameters","properties":{"category":{"description":"Corrected category (optional; omitted leaves it unchanged)","enum":["announcements","q_and_a","general"],"example":"announcements","type":"string"},"content":{"description":"Corrected post content (required)","example":"Kicking off week one — introduce yourself! (fixed image links)","maxLength":10000,"minLength":1,"type":"string"},"created_at":{"description":"Corrected posting time, ISO-8601. Truncated to seconds. Moves the post in the feed. A time in the future is rejected (422), as is one that lands after the stored `updated_at` — send `updated_at` too in that case.","example":"2024-03-01T12:00:00Z","format":"date-time","type":"string"},"updated_at":{"description":"Corrected edit time, ISO-8601. A time in the future, or one before `created_at`, is rejected (422).","example":"2024-03-04T08:15:00Z","format":"date-time","type":"string"}},"required":["content"],"type":"object"}},"required":["post"],"title":"AdminCommunityPostUpdate","type":"object"},"ProgressUpsert":{"description":"Parameters for creating or updating progress","example":{"progress":{"completed":false,"current_time":500,"duration":1000}},"properties":{"progress":{"properties":{"completed":{"default":false,"description":"Whether the video has been completed","type":"boolean"},"current_time":{"description":"Current playback position in seconds","minimum":0,"type":"integer"},"duration":{"description":"Total video duration in seconds","minimum":1,"type":"integer"}},"required":["current_time","duration"],"type":"object"}},"required":["progress"],"title":"ProgressUpsert","type":"object"},"AiSearchResponse":{"description":"Search results with AI synthesis","properties":{"content":{"description":"Synthesized answer","type":"string"},"interaction_id":{"description":"Logical search ID; null for previews or capture not admitted. Best-effort asynchronous persistence.","format":"uuid","nullable":true,"type":"string"},"query":{"description":"Original search query","type":"string"},"sources":{"description":"Source citations","items":{"$ref":"#/components/schemas/Citation"},"type":"array"}},"required":["content","sources","query"],"title":"AiSearchResponse","type":"object"},"UnifiedConversationSummary":{"description":"Conversation summary with scope indicator and optional video context","properties":{"conversation_id":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"message_count":{"type":"integer"},"preview":{"description":"Preview of first user message","nullable":true,"type":"string"},"scope":{"description":"Conversation scope","enum":["video","library"],"type":"string"},"status":{"type":"string"},"title":{"description":"Conversation title (auto-generated)","nullable":true,"type":"string"},"updated_at":{"format":"date-time","type":"string"},"video":{"anyOf":[{"$ref":"#/components/schemas/ConversationVideoContext"}],"description":"Video context for video-scoped conversations, null for library conversations","nullable":true}},"required":["conversation_id","scope","status","message_count","created_at","updated_at"],"title":"UnifiedConversationSummary","type":"object"},"ImageResponse":{"description":"Response after uploading an image","properties":{"data":{"description":"Uploaded image details","properties":{"content_type":{"description":"Sniffed content type (never the client-declared one)","example":"image/jpeg","type":"string"},"key":{"description":"Storage key","example":"uploads/bt_demo/images/abc123.jpg","type":"string"},"size":{"description":"File size in bytes","example":102400,"type":"integer"},"url":{"description":"Public URL of the stored image","example":"https://uploads.eu1.boldvideo.io/uploads/bt_demo/images/abc123.jpg","format":"uri","type":"string"}},"required":["url","key","content_type","size"],"type":"object"}},"required":["data"],"title":"ImageResponse","type":"object"},"RegisterDeviceRequest":{"description":"Push credentials for the device that owns this session. Registering again with a new token replaces the old one in place.","example":{"provider":"expo","token":"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"},"properties":{"provider":{"description":"Push provider for this token. Only `expo` is delivered today; `fcm` and `apns` are accepted and stored for clients that bypass Expo's relay.","enum":["expo","fcm","apns"],"type":"string"},"token":{"description":"The device push token. For `expo` this is the `ExponentPushToken[...]` string returned by `getExpoPushTokenAsync`; any other shape is rejected.","maxLength":255,"type":"string"}},"required":["provider","token"],"title":"RegisterDeviceRequest","type":"object"},"PortalSettings":{"description":"Portal configuration","properties":{"analytics":{"$ref":"#/components/schemas/AnalyticsSettings"},"auth":{"description":"Authentication configuration","type":"object"},"color_scheme":{"description":"Color scheme","enum":["light","dark","toggle"],"type":"string"},"custom_redirects":{"description":"Branded shortlink redirects","items":{"$ref":"#/components/schemas/CustomRedirect"},"type":"array"},"display":{"description":"Display configuration","type":"object"},"hero":{"$ref":"#/components/schemas/HeroSettings"},"layout":{"description":"Layout configuration","type":"object"},"navigation":{"description":"Navigation configuration","type":"object"},"theme":{"description":"Theme configuration","type":"object"}},"title":"PortalSettings","type":"object"},"AdminViewerSessionsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminSessionWithRevocation"},"type":"array"}},"required":["data"],"title":"AdminViewerSessionsResponse","type":"object"},"AuthSessionChallengeResponse":{"properties":{"challenge_id":{"format":"uuid","type":"string"},"challenge_required":{"type":"boolean"},"email_hint":{"description":"Masked destination email for the verification code.","nullable":true,"type":"string"}},"required":["challenge_required","challenge_id","email_hint"],"title":"AuthSessionChallengeResponse","type":"object"},"VideoChatReaction":{"properties":{"emoji":{"description":"Reaction emoji","type":"string"},"offset_ms":{"description":"Playback offset in milliseconds","minimum":0,"type":"integer"}},"required":["offset_ms","emoji"],"title":"VideoChatReaction","type":"object"},"AdminQuestionsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminQuestion"},"type":"array"},"meta":{"properties":{"page":{"type":"integer"},"page_size":{"type":"integer"},"since":{"format":"date-time","type":"string"},"total_entries":{"type":"integer"},"total_pages":{"type":"integer"},"until":{"format":"date-time","type":"string"}},"required":["page","page_size","total_entries","total_pages","since","until"],"type":"object"}},"required":["data","meta"],"title":"AdminQuestionsResponse","type":"object"},"Identity":{"description":"Account and non-secret API key identity","properties":{"data":{"properties":{"account":{"properties":{"name":{"example":"Acme","type":"string"},"slug":{"example":"acme","type":"string"}},"required":["slug","name"],"type":"object"},"features":{"description":"Account flags; viewer memory also requires viewers to be enabled","properties":{"mcp":{"type":"boolean"},"viewer_memory":{"type":"boolean"},"viewers":{"type":"boolean"}},"required":["viewers","viewer_memory","mcp"],"type":"object"},"key":{"properties":{"kind":{"enum":["tenant","admin"],"type":"string"},"short_id":{"description":"Non-secret key identifier","type":"string"}},"required":["kind","short_id"],"type":"object"}},"required":["account","key","features"],"type":"object"}},"required":["data"],"title":"Identity","type":"object"},"ViewerMemoryResponse":{"description":"Response containing a viewer's memory document.","properties":{"data":{"$ref":"#/components/schemas/ViewerMemory"}},"required":["data"],"title":"ViewerMemoryResponse","type":"object"},"AuthChallengeError":{"properties":{"attempts_remaining":{"nullable":true,"type":"integer"},"error":{"enum":["bad_request","challenge_invalid","challenge_expired","code_incorrect","too_many_attempts","resend_rate_limited","resend_limit_exceeded","viewer_email_required","challenge_delivery_unavailable","viewer_not_found","forbidden"],"type":"string"},"retry_after":{"nullable":true,"type":"integer"}},"required":["error"],"title":"AuthChallengeError","type":"object"},"AiImageUrl":{"description":"Image referenced by URL (will be fetched and validated)","properties":{"type":{"description":"Image type","enum":["url"],"type":"string"},"url":{"description":"HTTPS URL pointing to an image (max 20MB)","type":"string"}},"required":["type","url"],"title":"AiImageUrl","type":"object"},"VideoConversationListResponse":{"properties":{"conversations":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/ConversationPagination"},"video_id":{"type":"string"}},"required":["video_id","conversations","pagination"],"title":"VideoConversationListResponse","type":"object"},"AdminViewerResolveResponse":{"properties":{"data":{"properties":{"device_limit_override":{"nullable":true,"type":"integer"},"external_id":{"type":"string"},"session_management_exempt":{"type":"boolean"},"viewer_id":{"format":"uuid","type":"string"}},"required":["viewer_id","external_id","session_management_exempt"],"type":"object"}},"required":["data"],"title":"AdminViewerResolveResponse","type":"object"},"AdminQuestion":{"properties":{"asked_at":{"format":"date-time","type":"string"},"channel":{"enum":["portal","embed","api","mcp","unknown"],"type":"string"},"classified_at":{"format":"date-time","nullable":true,"type":"string"},"classifier_version":{"nullable":true,"type":"string"},"confidence":{"nullable":true,"type":"number"},"diagnosis":{"enum":["hard_to_find","missing_knowledge"],"nullable":true,"type":"string"},"diagnosis_version":{"nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"is_rephrase":{"nullable":true,"type":"boolean"},"kind":{"enum":["search","video_chat","library_ask"],"type":"string"},"outcome":{"enum":["answered","weak","not_answered","out_of_scope","noise"],"nullable":true,"type":"string"},"quality_reviewed_at":{"format":"date-time","nullable":true,"type":"string"},"quality_status":{"enum":["open","resolved","dismissed"],"type":"string"},"question":{"type":"string"},"reason":{"nullable":true,"type":"string"},"rephrase_of_id":{"format":"uuid","nullable":true,"type":"string"},"sources":{"description":"Cited videos (retrieved videos for searches), batch-resolved within the tenant. Resource-only and deleted videos omitted. null means unobserved; [] means observed empty.","items":{"properties":{"timestamp":{"description":"Start time in seconds","nullable":true,"type":"number"},"title":{"nullable":true,"type":"string"},"video_id":{"description":"Video internal_id","format":"uuid","type":"string"}},"required":["video_id","title","timestamp"],"type":"object"},"nullable":true,"type":"array"},"viewer":{"properties":{"display_name":{"description":"Audience Insights admin display name; never an email fallback","type":"string"},"id":{"format":"uuid","nullable":true,"type":"string"}},"required":["id","display_name"],"type":"object"}},"required":["id","asked_at","question","viewer","sources"],"title":"AdminQuestion","type":"object"},"CommunityPostResponse":{"description":"Response containing a single community post with comments","properties":{"data":{"$ref":"#/components/schemas/CommunityPost"},"mentions":{"$ref":"#/components/schemas/MentionsSkipped"}},"required":["data"],"title":"CommunityPostResponse","type":"object"},"SessionManagementExemptionRequest":{"properties":{"exempt":{"description":"Whether this viewer is exempt from session-management enforcement.","type":"boolean"}},"required":["exempt"],"title":"SessionManagementExemptionRequest","type":"object"},"CommunityCommentCreate":{"description":"Parameters for creating a comment","example":{"comment":{"content":"Great post! Thanks for sharing.","mentions":["outseta:AbC123"]}},"properties":{"comment":{"description":"Comment parameters","properties":{"content":{"description":"Comment content (required)","example":"Great post! Thanks for sharing.","type":"string"},"mentions":{"$ref":"#/components/schemas/MentionsParam"},"parent_id":{"description":"Parent comment UUID for replies (optional)","format":"uuid","nullable":true,"type":"string"}},"required":["content"],"type":"object"}},"required":["comment"],"title":"CommunityCommentCreate","type":"object"},"AdminCollectionVideosResponse":{"properties":{"already_member_ids":{"description":"Requested videos already in this collection on add; empty on remove","items":{"format":"uuid","type":"string"},"type":"array"},"data":{"allOf":[{"$ref":"#/components/schemas/Collection"}],"description":"Collection JSON; video_count includes all memberships on admin routes."},"membership_count":{"description":"Resulting total membership count","type":"integer"}},"required":["data","membership_count","already_member_ids"],"title":"AdminCollectionVideosResponse","type":"object"},"SearchResponse":{"description":"Search results for public videos and transcript content.","example":{"hits":[{"document_type":"transcript","duration_ms":5000,"id":"550e8400-e29b-41d4-a716-446655440000_1_5000-10000","speaker":"Speaker 1","text":"The first step is to connect your account...","timestamp_end_ms":10000,"timestamp_start_ms":5000,"title":"Getting Started","video_id":"550e8400-e29b-41d4-a716-446655440000"}],"page":1,"per_page":10,"processing_time_ms":4,"total_hits":1},"properties":{"hits":{"description":"Matching search hits","items":{"$ref":"#/components/schemas/SearchHit"},"type":"array"},"interaction_id":{"description":"Logical search ID; null for previews, blank searches or capture not admitted. Best-effort asynchronous persistence.","format":"uuid","nullable":true,"type":"string"},"page":{"description":"Current page number","type":"integer"},"per_page":{"description":"Results per page","type":"integer"},"processing_time_ms":{"description":"Search processing time in milliseconds","type":"integer"},"total_hits":{"description":"Estimated total matching hits","type":"integer"}},"required":["hits","page","per_page","total_hits","processing_time_ms"],"title":"SearchResponse","type":"object"},"SearchHit":{"description":"A raw Meilisearch hit for a matching transcript segment or indexed video document.","properties":{"_rankingScore":{"description":"Meilisearch ranking score when returned","nullable":true,"type":"number"},"document_type":{"description":"Indexed document type, such as transcript or attachment","nullable":true,"type":"string"},"duration_ms":{"description":"Segment duration in milliseconds","nullable":true,"type":"integer"},"id":{"description":"Search document ID","type":"string"},"speaker":{"description":"Speaker name when identified, otherwise the diarization label.","nullable":true,"type":"string"},"speaker_label":{"description":"Raw diarization label, or null.","nullable":true,"type":"string"},"speaker_name":{"description":"Resolved speaker name, or null.","nullable":true,"type":"string"},"text":{"description":"Matched transcript text","nullable":true,"type":"string"},"timestamp_end_ms":{"description":"Segment end time in milliseconds","nullable":true,"type":"integer"},"timestamp_start_ms":{"description":"Segment start time in milliseconds","nullable":true,"type":"integer"},"title":{"description":"Source video title","nullable":true,"type":"string"},"video_id":{"description":"Source video UUID","type":"string"}},"title":"SearchHit","type":"object"},"AuthChallengeResendResponse":{"properties":{"challenge_id":{"format":"uuid","type":"string"},"email_hint":{"nullable":true,"type":"string"},"expires_at":{"format":"date-time","type":"string"},"ok":{"type":"boolean"},"resend_count":{"type":"integer"}},"required":["ok","challenge_id","email_hint","resend_count","expires_at"],"title":"AuthChallengeResendResponse","type":"object"},"CollectionVideosResponse":{"description":"Response containing videos in a collection","properties":{"data":{"description":"List of videos in the collection","items":{"$ref":"#/components/schemas/Video"},"type":"array"}},"required":["data"],"title":"CollectionVideosResponse","type":"object"},"AiChatRequest":{"description":"Request body for AI chat endpoints","example":{"prompt":"What are the key takeaways from this video?","stream":false},"properties":{"channel":{"description":"Optional caller channel for analytics. Missing or unrecognized values are recorded as `unknown` and never reject the AI request.","enum":["portal","embed","api","mcp"],"type":"string"},"client_name":{"description":"Optional analytics client name. Trimmed values must be 1–128 characters and at most 512 UTF-8 bytes; invalid values become unknown and never reject the request.","maxLength":128,"minLength":1,"type":"string"},"client_version":{"description":"Optional analytics client version. Trimmed values must be 1–128 characters and at most 512 UTF-8 bytes; invalid values become unknown and never reject the request.","maxLength":128,"minLength":1,"type":"string"},"conversation_id":{"description":"Existing conversation ID to continue","nullable":true,"type":"string"},"current_time":{"description":"Current video playback position in seconds for contextual answers","nullable":true,"type":"integer"},"images":{"description":"Optional array of images (max 5). Requires the Vision feature flag. Each image can be base64-encoded or a URL.","items":{"anyOf":[{"$ref":"#/components/schemas/AiImageBase64"},{"$ref":"#/components/schemas/AiImageUrl"}]},"maxItems":5,"nullable":true,"type":"array"},"max_tokens":{"description":"Maximum tokens in response","nullable":true,"type":"integer"},"model":{"description":"AI model to use (optional, uses account default)","nullable":true,"type":"string"},"prompt":{"description":"The user's question or prompt","example":"What are the main topics discussed in this video?","type":"string"},"request_id":{"description":"Caller action UUID. Reuse unchanged on retries to deduplicate analytics, not model execution. Omission or an invalid UUID creates a new interaction.","format":"uuid","type":"string"},"stream":{"default":true,"description":"Whether to stream the response via SSE (default: true)","type":"boolean"},"temperature":{"description":"Temperature for response generation (0.0-2.0)","format":"float","nullable":true,"type":"number"},"viewer":{"description":"Viewer identifier (UUID or your external ID) to associate with the conversation. When the viewers feature is enabled, the viewer's traits also personalize AI responses.","nullable":true,"type":"string"},"viewer_profile":{"anyOf":[{"description":"Free text or markdown describing the viewer; replaces the synced viewer's traits for this request. Up to 8000 characters.","example":"Sarah runs a 3-person agency, pre-launch, wants her first 10 customers.","type":"string"},{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"description":"Key-value traits merged over the synced viewer's traits (inline wins per key). Max 20 keys; keys must match ^[a-zA-Z_][a-zA-Z0-9_]*$; values are strings (up to 2000 characters), numbers, or booleans.","example":{"business_name":"Acme Corp","goal":"find first customers","stage":"growth"},"type":"object"}],"description":"Tell the AI about the person asking, for this request only (never persisted). Pass free text/markdown, or a key-value object. Requires the viewers feature.","nullable":true}},"required":["prompt"],"title":"AiChatRequest","type":"object"},"AdminCommunityCommentCreate":{"description":"Parameters for importing a comment","example":{"comment":{"author":{"external_id":"wQXZPRWZ","name":"John Roe"},"content":"Welcome aboard!","created_at":"2024-03-02T09:30:00Z","external_ref":"kajabi:comment:4471"}},"properties":{"comment":{"description":"Comment parameters","properties":{"author":{"$ref":"#/components/schemas/CommunityImportAuthor"},"content":{"description":"Comment content (required)","example":"Welcome aboard!","type":"string"},"created_at":{"description":"Original posting time, ISO-8601. Truncated to seconds. A time in the future is rejected (422). Sets `updated_at` too unless that is sent separately.","example":"2024-03-02T09:30:00Z","format":"date-time","type":"string"},"external_ref":{"description":"Idempotency key from the source system. Replaying it returns 200 with the stored comment.","example":"kajabi:comment:4471","type":"string"},"mentions":{"$ref":"#/components/schemas/MentionsParam"},"parent_id":{"description":"Parent comment UUID for replies (optional, max depth 2)","format":"uuid","nullable":true,"type":"string"},"reactions":{"$ref":"#/components/schemas/CommunityImportReactions"},"updated_at":{"description":"Time the source comment was last edited, ISO-8601. Optional: omit it and the comment reads as written once and never edited. Requires `created_at` — sent on its own it is rejected (422), because the creation time would then be stamped at import and land after the edit. A time in the future, or one before `created_at`, is rejected (422).","example":"2024-03-02T10:00:00Z","format":"date-time","type":"string"}},"required":["content","author"],"type":"object"}},"required":["comment"],"title":"AdminCommunityCommentCreate","type":"object"},"PlaylistResponse":{"description":"Single playlist with videos","properties":{"data":{"$ref":"#/components/schemas/Playlist"}},"required":["data"],"title":"PlaylistResponse","type":"object"},"ConversationStarter":{"description":"A conversation starter resolved from a collection or the account-level list","properties":{"collection_id":{"description":"The collection this starter belongs to, or null for account-sourced starters","format":"uuid","nullable":true,"type":"string"},"source":{"description":"Whether this starter came from the collection's own list or the account fallback","enum":["collection","account"],"type":"string"},"text":{"description":"The starter text","example":"What labs before starting a GLP-1?","type":"string"}},"required":["text","source","collection_id"],"title":"ConversationStarter","type":"object"},"AdminVideoPublishRequest":{"additionalProperties":false,"properties":{"published_at":{"description":"Past timestamps are preserved; null/future timestamps become now","format":"date-time","nullable":true,"type":"string"}},"title":"AdminVideoPublishRequest","type":"object"},"AuthSessionsListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ActiveSessionSummary"},"type":"array"}},"required":["data"],"title":"AuthSessionsListResponse","type":"object"},"AnalyticsSettings":{"description":"Analytics provider configuration","properties":{"provider":{"description":"Analytics provider","enum":["none","plausible","ga4","fathom"],"example":"none","type":"string"},"site_id":{"description":"Analytics site ID","nullable":true,"type":"string"}},"required":["provider"],"title":"AnalyticsSettings","type":"object"},"AdminImportsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminImportRun"},"type":"array"},"meta":{"properties":{"page":{"type":"integer"},"page_size":{"type":"integer"},"total_entries":{"type":"integer"},"total_pages":{"type":"integer"}},"type":"object"}},"required":["data","meta"],"title":"AdminImportsResponse","type":"object"},"Playlist":{"description":"A curated list of videos","properties":{"cover_image_url":{"description":"Cover image URL","nullable":true,"type":"string"},"description":{"description":"Playlist description","nullable":true,"type":"string"},"id":{"description":"Playlist ID","type":"string"},"is_private":{"description":"Whether the playlist is private","type":"boolean"},"title":{"description":"Playlist title","example":"Getting Started","type":"string"},"videos":{"description":"Videos in this playlist","items":{"$ref":"#/components/schemas/Video"},"type":"array"}},"required":["id","title","is_private"],"title":"Playlist","type":"object"},"ViewerMemory":{"description":"A viewer's AI-maintained memory document, distilled from their conversations.","properties":{"content":{"description":"The distilled memory document. AI-maintained; cleared via DELETE; regrows from future conversations unless `enabled` is false. Null when nothing has been distilled yet.","example":"Situation & goals:\n- Runs a 3-person agency scaling to $1M ARR.","nullable":true,"type":"string"},"conversations_count":{"description":"Number of summarized conversations that fed this memory.","example":4,"type":"integer"},"enabled":{"description":"Whether memory distillation is active for this viewer. Set false via PATCH to opt out (re-distillation stops; existing content is retained until cleared).","example":true,"type":"boolean"},"last_conversation_at":{"description":"Timestamp of the most recent conversation activity.","format":"date-time","nullable":true,"type":"string"},"last_distilled_at":{"description":"When the memory document was last rewritten.","format":"date-time","nullable":true,"type":"string"},"updated_at":{"description":"Last update timestamp for the memory row.","format":"date-time","nullable":true,"type":"string"}},"required":["content","enabled","conversations_count"],"title":"ViewerMemory","type":"object"},"MentionsUnreadCountResponse":{"description":"Number of unread mentions for the current viewer","example":{"data":{"count":3}},"properties":{"data":{"properties":{"count":{"description":"Unread mention count","example":3,"type":"integer"}},"required":["count"],"type":"object"}},"required":["data"],"title":"MentionsUnreadCountResponse","type":"object"},"AiImageBase64":{"description":"Base64-encoded image","properties":{"data":{"description":"Base64-encoded image data (max 20MB decoded)","type":"string"},"media_type":{"description":"MIME type of the image","enum":["image/png","image/jpeg","image/webp","image/gif"],"type":"string"},"type":{"description":"Image type","enum":["base64"],"type":"string"}},"required":["type","media_type","data"],"title":"AiImageBase64","type":"object"},"AdminSessionWithRevocation":{"properties":{"device_id":{"type":"string"},"device_label":{"description":"Server-derived human-readable label for the device/browser and OS (e.g. \"Chrome · macOS\", \"Safari · iPhone\"). Derived from the `user_agent` supplied at session creation. Nullable when the user_agent is absent or unrecognised; callers should fall back to `device_id` for display.","nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"last_seen_at":{"format":"date-time","type":"string"},"location":{"description":"Coarse, pre-formatted client location (e.g. \"Berlin, DE\" or \"DE\"). Never the raw IP.","nullable":true,"type":"string"},"platform":{"type":"string"},"revoked_at":{"format":"date-time","nullable":true,"type":"string"},"revoked_by":{"nullable":true,"type":"string"},"revoked_reason":{"nullable":true,"type":"string"},"travel_verdict":{"description":"Impossible-travel verdict label. \"impossible_travel\" when the sign-in was flagged as physically implausible, otherwise null. Label only — never exposes coordinates, the raw IP, or derived distance/speed metrics.","nullable":true,"type":"string"}},"title":"AdminSessionWithRevocation","type":"object"},"MentionsSkipped":{"description":"Present only when the request carried a `mentions` array. `skipped` echoes back the\nsupplied ids that were **not** recorded — blank or malformed ids, ids beyond the\n25-id cap, and anything Bold could not resolve. A self-mention is dropped silently\nand duplicates collapse, so neither appears here. A bad id never fails the write.\n","properties":{"skipped":{"description":"Supplied mention ids that were not recorded","example":["","outseta:unknown-shape"],"items":{"type":"string"},"type":"array"}},"required":["skipped"],"title":"MentionsSkipped","type":"object"},"MentionsParam":{"description":"Mentioned members, given as **customer external ids** (e.g. Outseta uids) — never Bold\nviewer ids, because a mentioned member may not have a viewer yet. Bold resolves each id\nto a viewer, creating one it has never seen, and stores the mention rows in the same\ntransaction as the write.\n\nCapped at 25 ids. Duplicates collapse, a self-mention is dropped, and any id Bold could\nnot record is returned in `mentions.skipped` — a bad id never fails the write.\n\nBold does not parse `content`: it neither looks for `@handles` nor checks that a passed\nmention appears in the text.\n","example":["outseta:AbC123","outseta:XyZ789"],"items":{"type":"string"},"maxItems":25,"title":"MentionsParam","type":"array"},"CommunityPostUpdate":{"description":"Editable post fields; ownership, pins and import metadata cannot be changed","properties":{"post":{"properties":{"category":{"enum":["announcements","q_and_a","general"],"type":"string"},"content":{"maxLength":10000,"minLength":1,"type":"string"},"mentions":{"$ref":"#/components/schemas/MentionsParam"}},"type":"object"}},"required":["post"],"title":"CommunityPostUpdate","type":"object"},"AdminVideo":{"properties":{"collections":{"items":{"properties":{"id":{"format":"uuid","type":"string"},"name":{"type":"string"}},"required":["id","name"],"type":"object"},"type":"array"},"created_at":{"description":"UTC creation timestamp","type":"string"},"duration":{"description":"Duration in seconds","nullable":true,"type":"number"},"external_id":{"description":"Recorded imported_id","nullable":true,"type":"string"},"id":{"description":"Legacy hashid","type":"string"},"import_source":{"description":"Recorded imported_from value; no inferred source","nullable":true,"type":"string"},"internal_id":{"description":"Primary admin video identifier","format":"uuid","type":"string"},"privacy_level":{"enum":["public","unlisted","private"],"type":"string"},"published_at":{"description":"UTC publication timestamp; null for unpublished videos","nullable":true,"type":"string"},"slug":{"nullable":true,"type":"string"},"status":{"description":"Stored video processing status; independent of transcript/index readiness","enum":["novideo","waiting","queued","uploading","transfer_scheduled","transfer_started","transferring","ready_for_processing","preparing","processing","zoom_processing","generating_ai_metadata","transcribed","ready","done","failed","import_failed","transfer_failed","encoding_error","errored","error"],"nullable":true,"type":"string"},"tags":{"items":{"properties":{"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"slug":{"type":"string"}},"required":["id","name","slug"],"type":"object"},"type":"array"},"title":{"nullable":true,"type":"string"},"updated_at":{"description":"UTC last-update timestamp","type":"string"},"video_type":{"description":"Active video type UUID (active_video_type_id)","format":"uuid","nullable":true,"type":"string"}},"required":["internal_id","id","slug","title","status","privacy_level","published_at","tags","collections","video_type","duration","import_source","external_id","created_at","updated_at"],"title":"AdminVideo","type":"object"},"AiChatResponse":{"description":"JSON response from AI chat (when stream=false)","properties":{"content":{"description":"The AI's response content","type":"string"},"conversation_id":{"description":"Conversation ID for continuing the chat","type":"string"},"id":{"description":"Deprecated: Use conversation_id instead","type":"string"},"interaction_id":{"description":"Logical turn ID. Best-effort asynchronous capture, not a persistence acknowledgement.","format":"uuid","type":"string"},"model":{"description":"The AI model used","example":"claude-3-5-sonnet-20241022","type":"string"},"usage":{"description":"Token usage statistics","properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"}},"type":"object"},"video_id":{"description":"The video's internal ID","type":"string"}},"required":["conversation_id","content","video_id"],"title":"AiChatResponse","type":"object"}},"securitySchemes":{"admin_api_token":{"description":"Privileged admin key (bold_live_* with admin: true) for Mission Control and support automation. Do not put this key in customer-owned apps.","in":"header","name":"X-Bold-Admin-Api-Key","type":"apiKey"},"api_token":{"description":"API token provided by BOLD Admin","scheme":"bearer","type":"http"},"upstream_jwt":{"bearerFormat":"JWT","description":"Customer IdP JWT used by session-management client endpoints. Must match the tenant policy's issuer/audience/JWKS settings.","scheme":"bearer","type":"http"}}},"info":{"description":"API for accessing video content, collections, playlists, and AI-powered features.\n\n## Authentication\n\nMost API endpoints require authentication via a BOLD API token. Include your token\nin the `Authorization` header as a bearer token:\n\n```\nAuthorization: Bearer YOUR_API_TOKEN\n```\n\nSession-management client endpoints under `/api/v1/auth/*` use the customer's\nupstream IdP JWT instead:\n\n```\nAuthorization: Bearer UPSTREAM_IDP_JWT\nX-Bold-Tenant-Slug: YOUR_TENANT_SLUG\n```\n\nServer-side session-management integration endpoints under\n`/api/v1/session-management/*` use the normal tenant BOLD API token. These\nendpoints are for customer-owned servers to inspect and revoke device sessions\nafter the customer app has already decided the viewer is allowed to access content.\n\nAdmin endpoints under `/api/v1/admin/*` require `X-Bold-Admin-Api-Key` and are\nreserved for Mission Control or deliberately privileged support automation.\n","title":"BoldAdmin API","version":"1.0.0"},"openapi":"3.0.0","paths":{"/api/v1/community/mentions/unread-count":{"get":{"callbacks":{},"description":"Returns the number of mentions of the `X-Viewer-ID` viewer that have not been marked read. Intended for a badge, so it is a single indexed count.","operationId":"BoldAdminWeb.Api.CommunityController.unread_count","parameters":[{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MentionsUnreadCountResponse"}}},"description":"Unread mention count"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized - X-Viewer-ID header required"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Count unread mentions of the current viewer","tags":["Community"]}},"/api/v1/viewers/{viewer_id}/memory":{"delete":{"callbacks":{},"description":"Clears the viewer's memory content (idempotent). The document regrows from future conversations unless the viewer is also opted out via PATCH `{\"memory\": {\"enabled\": false}}`. Requires the `viewer_memory` feature.","operationId":"BoldAdminWeb.Api.ViewerMemoryController.delete","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Memory cleared"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers/viewer_memory feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"Clear a viewer's memory","tags":["Viewers"]},"get":{"callbacks":{},"description":"Returns the AI-maintained memory document for a viewer. If nothing has been distilled yet, returns an empty default (content null, enabled true). Requires the `viewer_memory` feature.","operationId":"BoldAdminWeb.Api.ViewerMemoryController.show","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerMemoryResponse"}}},"description":"Viewer memory"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers/viewer_memory feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"Get a viewer's memory","tags":["Viewers"]},"patch":{"callbacks":{},"description":"Opts a viewer in or out of memory re-distillation via `{\"memory\": {\"enabled\": bool}}`. Opting out stops future distillation but retains existing content until cleared. Requires the `viewer_memory` feature.","operationId":"BoldAdminWeb.Api.ViewerMemoryController.update","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerMemoryUpdate"}}},"description":"Memory settings","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerMemoryResponse"}}},"description":"Updated viewer memory"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers/viewer_memory feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Update a viewer's memory settings","tags":["Viewers"]}},"/api/v1/admin/viewers/by-external-id/{external_id}":{"get":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Admin.ViewerController.by_external_id","parameters":[{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminViewerResolveResponse"}}},"description":"Resolved viewer"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"}},"security":[{"admin_api_token":[]}],"summary":"Resolve an upstream IdP user ID to a Bold viewer_id","tags":["Admin Viewers"]}},"/api/v1/admin/viewers/{viewer_id}/sessions/{id}/revoke":{"post":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Admin.ViewerSessionController.revoke","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}},{"description":"Session UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRevokeAuthSessionRequest"}}},"description":"Optional reason","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRevokeAuthSessionResponse"}}},"description":"Session revoked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session not found"}},"security":[{"admin_api_token":[]}],"summary":"Admin-revoke a single session","tags":["Admin Auth Sessions"]}},"/api/v1/admin/insights/questions":{"get":{"callbacks":{},"description":"Audience Insights activity, newest first with ID tie-breaking. Requires an admin key; includes searches as in the Analytics stream. since is inclusive, until exclusive. Dates mean midnight UTC; positive Nd/Nh durations are relative to the same request time. Defaults: since=7d, until=now. Echoed absolute bounds can be reused for a digest. Ordering is deterministic, not a historical snapshot: classification, review and erasure may change stored results. Noise, pending classification/diagnosis and team/test are hidden by default. Viewer names follow the admin Audience Insights display policy. Video filters and sources may reference private/unpublished videos, only on this admin surface.","operationId":"BoldAdminWeb.Api.Admin.InsightsController.questions","parameters":[{"description":"Inclusive ISO timestamp, UTC date or duration; default 7d","in":"query","name":"since","required":false,"schema":{"type":"string"}},{"description":"Exclusive ISO timestamp, UTC date or duration; default now","in":"query","name":"until","required":false,"schema":{"type":"string"}},{"description":"Interaction collection UUID","in":"query","name":"collection","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Interaction video UUID (preferred), hashid or slug","in":"query","name":"video","required":false,"schema":{"type":"string"}},{"description":"Page number","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Items per page (capped at 100)","in":"query","name":"page_size","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Audience Insights reason filter","in":"query","name":"reason","required":false,"schema":{"enum":["hard_to_find","low_confidence","error","slow"],"type":"string"}},{"description":"Audience Insights status filter","in":"query","name":"status","required":false,"schema":{"enum":["open","resolved","dismissed"],"type":"string"}},{"description":"Audience Insights kind filter","in":"query","name":"kind","required":false,"schema":{"enum":["search","video_chat","library_ask"],"type":"string"}},{"description":"Audience Insights outcome filter","in":"query","name":"outcome","required":false,"schema":{"enum":["answered","weak","missing","hard_to_find","outside"],"type":"string"}},{"description":"Audience Insights channel filter","in":"query","name":"channel","required":false,"schema":{"enum":["portal","embed","api","mcp","unknown"],"type":"string"}},{"description":"Audience Insights needs_a_look filter","in":"query","name":"needs_a_look","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Audience Insights include_team filter","in":"query","name":"include_team","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Audience Insights include_noise filter","in":"query","name":"include_noise","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"Audience Insights include_pending filter","in":"query","name":"include_pending","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminQuestionsResponse"}}},"description":"Viewer questions"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid filters or window"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"}},"security":[{"admin_api_token":[]}],"summary":"Read viewer questions (admin)","tags":["Admin Insights"]}},"/api/v1/collections/{collection_id}/videos":{"get":{"callbacks":{},"description":"Returns all videos belonging to a collection.","operationId":"BoldAdminWeb.Api.CollectionController.videos","parameters":[{"description":"Collection UUID or slug","in":"path","name":"collection_id","required":true,"schema":{"type":"string"}},{"description":"Maximum number of videos (1-1000, default: 100)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Offset for pagination (default: 0)","in":"query","name":"offset","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionVideosResponse"}}},"description":"Collection videos"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Collection not found"}},"summary":"List videos in a collection","tags":["Collections"]}},"/api/v1/event":{"post":{"callbacks":{},"description":"SDK analytics beacon. `n=source_open` and `n=video_progress` payloads containing\n`interaction_id` or `playback_id` use the audience path. Other event names retain\nlegacy handling even with extra ID fields. The authenticated tenant is authoritative.\n\n`source_open` records the server receipt time for the first open. Progress must\nfollow an explicit open and reports cumulative elapsed PLAYING wall-clock time,\nnot playhead position. Timestamped citations must retain the cited video's `vid`.\nDelivery and capture are best effort: authenticated active accounts receive 202, not a durable\nacknowledgement; retries may continue for about 31 seconds, but restart,\noverload, or expiry can lose data.\n","operationId":"BoldAdminWeb.Api.MetricsController.event","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsEventRequest"}}},"description":"Legacy analytics or dedicated audience beacon","required":true},"responses":{"202":{"description":"Accepted; empty body, not a persistence acknowledgement"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid API token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Track event","tags":["Analytics"]}},"/api/v1/admin/collections/{id}":{"patch":{"callbacks":{},"description":"Partially updates existing schema fields. Omitted fields are unchanged. Requires an admin key.","operationId":"BoldAdminWeb.Api.Admin.CollectionController.update","parameters":[{"description":"Collection UUID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionRequest"}}},"description":"Collection fields","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponse"}}},"description":"Collection updated"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Collection not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid collection or parent cycle"}},"security":[{"admin_api_token":[]}],"summary":"Update a collection (admin)","tags":["Admin Collections"]}},"/api/v1/auth/sessions/revoke-others":{"post":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Auth.SessionController.revoke_others","parameters":[{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}},{"description":"ID of the session to keep","in":"header","name":"x-bold-session-id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeOthersResponse"}}},"description":"Other sessions revoked"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing X-Bold-Session-Id header"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfManagementDisabledError"}}},"description":"Self-management disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer or keep-session not found"}},"security":[{"upstream_jwt":[]}],"summary":"Revoke all of the authenticated viewer's sessions except the current one","tags":["Auth Sessions"]}},"/api/v1/videos":{"get":{"callbacks":{},"description":"Returns a paginated list of public videos for the current tenant. Signed playback includes Mux playback and storyboard tokens and their playback expiration; public playback tokens are null.","operationId":"BoldAdminWeb.Api.VideoController.index (2)","parameters":[{"description":"Page number","in":"query","name":"page","required":false,"schema":{"type":"integer"}},{"description":"Filter by tag","in":"query","name":"tag","required":false,"schema":{"type":"string"}},{"description":"Filter by a single collection UUID","in":"query","name":"collection_id","required":false,"schema":{"type":"string"}},{"description":"Filter by multiple collection UUIDs (OR semantics). Supports repeated query params and comma-separated values.","explode":true,"in":"query","name":"collection_ids","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Only return uncategorized videos","in":"query","name":"no_collection","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosResponse"}}},"description":"Videos list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"List public videos","tags":["Videos"]},"post":{"callbacks":{},"description":"Accepts a video URL for ingestion. Returns immediately with a video ID. Poll GET /api/v1/videos/:id for processing status.","operationId":"BoldAdminWeb.Api.VideoController.create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoIngestRequest"}}},"description":"Video ingestion request","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoIngestResponse"}}},"description":"Existing video (idempotent)"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoIngestResponse"}}},"description":"Video created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Collection not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Create a video from URL","tags":["Videos"]}},"/api/v1/viewers/{viewer_id}/progress":{"get":{"callbacks":{},"description":"Returns all progress records for a viewer with metadata statistics.","operationId":"BoldAdminWeb.Api.ProgressController.index","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}},{"description":"Filter by completion status","in":"query","name":"completed","required":false,"schema":{"type":"boolean"}},{"description":"Filter by collection UUID","in":"query","name":"collection_id","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgressListResponse"}}},"description":"Progress list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"List viewer progress","tags":["Progress"]}},"/api/v1/auth/sessions/{id}/device":{"post":{"callbacks":{},"description":"Attaches a push token to one of the authenticated viewer's active sessions. The token lives on the session, so revoking that session stops its pushes; re-registering the same device updates the token in place. Requires session management to be enabled for the account and its policy to be out of the `off` phase — without sessions there is nothing to attach a token to.","operationId":"BoldAdminWeb.Api.Auth.SessionDeviceController.create","parameters":[{"description":"Session UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeviceRequest"}}},"description":"Device registration payload","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeviceResponse"}}},"description":"Device registered"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing or malformed provider/token"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session management not available for this account"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session not found, revoked, or not owned by the viewer"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"security":[{"upstream_jwt":[]}],"summary":"Register a device push token on a session","tags":["Auth Sessions"]}},"/api/v1/admin/viewers/{viewer_id}/sessions/revoke-all":{"post":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Admin.ViewerSessionController.revoke_all","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRevokeAuthSessionRequest"}}},"description":"Optional reason","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRevokeAllResponse"}}},"description":"All sessions revoked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"security":[{"admin_api_token":[]}],"summary":"Admin-revoke every active session for a viewer","tags":["Admin Auth Sessions"]}},"/api/v1/auth/challenges/{id}/verify":{"post":{"callbacks":{},"description":"Verifies a short-lived email code for the authenticated viewer and materializes the pending auth session if the code is valid.","operationId":"BoldAdminWeb.Api.Auth.ChallengeController.verify","parameters":[{"description":"Challenge UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAuthChallengeRequest"}}},"description":"Challenge verification payload","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSessionResponse"}}},"description":"Session created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Invalid, expired, or incorrect challenge code"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Challenge ownership mismatch"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Viewer not provisioned"}},"security":[{"upstream_jwt":[]}],"summary":"Verify an auth challenge code","tags":["Auth Challenges"]}},"/api/v1/ai/chat/{id}":{"delete":{"callbacks":{},"description":"Delete a library-scoped conversation and all associated messages.","operationId":"BoldAdminWeb.Api.AskStreamController.delete","parameters":[{"description":"Conversation ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Conversation deleted"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI feature not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conversation not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deletion failed"}},"summary":"Delete a library chat conversation","tags":["AI Chat"]},"get":{"callbacks":{},"description":"Retrieve the full message history for a library conversation. Sources carry fresh playback tokens for signed videos with a nonempty Mux playback ID, null otherwise. Video conversations must use their video-scoped history endpoint and access checks. Each assistant message includes its exact persisted interaction_id when available; otherwise null.","operationId":"BoldAdminWeb.Api.AskStreamController.show (4)","parameters":[{"description":"Conversation ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationResponse"}}},"description":"Conversation"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — account inactive (subscription expired or no active plan) or required feature disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conversation not found"}},"summary":"Get conversation history","tags":["AI Chat"]},"post":{"callbacks":{},"description":"Continue an existing conversation with follow-up questions.\nVideo sources include a fresh playback_token for signed playback; public and non-playback sources use null.\n\nThe AI maintains context from previous messages in the conversation.\nJSON and `message_complete` include `interaction_id`. Reuse a UUID `request_id`\nfor retries of the same turn. This deduplicates best-effort asynchronous\nanalytics, not model execution or responses.\nOptional channel/client metadata is outside retry identity; invalid values are\nrecorded as unknown and never reject the request.\nError responses omit internal provider and exception details.\nAnswer responses include public-safe retrieval metadata: `retrieval_mode`, `degraded`,\nand `degraded_reason`.\n\nWhen `synthesize` is false, successful retrieval returns ordered uncited evidence with\n`response_type: \"retrieval\"`. SSE uses one terminal `message_complete` event carrying that\nretrieval result. Query planning can still return a clarification response before retrieval.\n\nAnswers are finalized the same way as \"Chat with your video library\": claims\ncarrying unknown citation markers are removed, exact cited video titles become\nMarkdown links, and the terminal `answer` event is authoritative over\nconcatenated `text_delta` chunks.\n","operationId":"BoldAdminWeb.Api.AskStreamController.continue_ask (2)","parameters":[{"description":"Conversation ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryAiChatRequest"}}},"description":"Chat request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryAiChatResponse"}},"text/event-stream":{"schema":{"$ref":"#/components/schemas/AiStreamEvent"}}},"description":"Chat response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid request"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — account inactive (subscription expired or no active plan) or required feature disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conversation not found"}},"summary":"Continue a library chat","tags":["AI Chat"]}},"/api/v1/community/comments/{id}":{"delete":{"callbacks":{},"description":"Deletes an existing comment. Requires the `X-Viewer-ID` header.\n\nOnly the comment author or an admin viewer can delete a comment.\n\nBy default, replies cascade for compatibility. Set preserve_replies=true to\nkeep replies: a parent becomes a placeholder with empty content, null author and deleted_at;\na leaf is removed. Counts and commenter previews include live comments only.\nRetrying deletion of a placeholder is a no-op. Empty placeholder ancestors\nare removed when their final reply is deleted.\n","operationId":"BoldAdminWeb.Api.CommunityController.delete_comment","parameters":[{"description":"Comment UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Keep replies using a deleted-comment placeholder","in":"query","name":"preserve_replies","required":false,"schema":{"type":"boolean"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Comment deleted"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid preserve_replies value"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden - not comment owner or admin"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Comment not found"}},"summary":"Delete a comment","tags":["Community"]}},"/api/v1/viewers/{viewer_id}/progress/{video_id}/complete":{"post":{"callbacks":{},"description":"Marks a video as completed for the viewer. Sets completed=true, current_time=duration, and records completion timestamp. Progress must already exist.","operationId":"BoldAdminWeb.Api.ProgressController.complete","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}},{"description":"Video UUID","in":"path","name":"video_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgressResponse"}}},"description":"Video marked as complete"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Progress not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Mark video as complete","tags":["Progress"]}},"/api/v1/ai/recommendations":{"post":{"callbacks":{},"description":"Get AI-powered video recommendations based on topics or learning goals.\nRecommended videos include playback_id and a fresh playback_token for signed playback (null for public playback).\n\nOptionally includes guidance text explaining why each video was recommended.\n\n**Streaming (default):** Returns SSE events:\n- `message_start`: Stream started\n- `text_delta`: Guidance text chunk\n- `recommendations`: Video list\n- `message_complete`: Complete response\n\nSet `stream=false` for a single JSON response.\n","operationId":"BoldAdminWeb.Api.RecommendController.recommend","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiRecommendationsRequest"}}},"description":"Recommendations request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiRecommendationsResponse"}}},"description":"Recommendations"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid topics"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI not enabled"}},"summary":"Get video recommendations","tags":["AI Recommendations"]}},"/api/v1/images":{"post":{"callbacks":{},"description":"Uploads an image and stores it in Bold's public bucket, returning a stable\n`uploads.eu1.boldvideo.io` URL.\n\nThe file is sniffed by magic bytes against an allowlist (jpeg, png, gif, webp,\navif) — the client-declared content type is never trusted. SVG is rejected.\nMaximum file size is 5 MB.\n\nRequires the `X-Viewer-ID` header to identify the uploading viewer.\n","operationId":"BoldAdminWeb.Api.ImageController.create","parameters":[{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/ImageUploadRequest"}}},"description":"Image file","required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageResponse"}}},"description":"Image uploaded"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unsupported media type or missing file"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized - X-Viewer-ID header required or viewer not found"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"File too large (max 5 MB)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Upload failed"}},"summary":"Upload an image","tags":["Images"]}},"/api/v1/videos/{id}/chat":{"get":{"callbacks":{},"description":"Returns the recorded meeting chat with participant names rendered according to the\naccount's asker-display policy. Reactions never include an author.\n\nThe chat is available only when an admin has made it visible and the video is public,\npublished, and not in a processing state. Hidden and absent chats return the same 404.\n","operationId":"BoldAdminWeb.Api.VideoController.chat","parameters":[{"description":"Video ID (hashid, UUID) or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoChatResponse"}}},"description":"Meeting chat"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Chat not found"}},"summary":"Get a video's meeting chat","tags":["Videos"]}},"/api/v1/viewers":{"get":{"callbacks":{},"description":"Returns a paginated list of viewers for the current tenant.","operationId":"BoldAdminWeb.Api.ViewerController.index","parameters":[{"description":"Page number (default: 1)","example":1,"in":"query","name":"page","required":false,"schema":{"type":"integer"}},{"description":"Items per page (default: 20)","example":20,"in":"query","name":"page_size","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewersResponse"}}},"description":"Viewers list with pagination metadata"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"}},"summary":"List viewers (paginated)","tags":["Viewers"]},"post":{"callbacks":{},"description":"Creates a new viewer for the current tenant.","operationId":"BoldAdminWeb.Api.ViewerController.create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerCreate"}}},"description":"Viewer parameters","required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerResponse"}}},"description":"Viewer created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Create a viewer","tags":["Viewers"]}},"/api/v1/community/mentions":{"get":{"callbacks":{},"description":"Returns the mentions of the viewer identified by the `X-Viewer-ID` header, newest\nfirst. Each entry carries everything an inbox row needs — the post (and comment, when\nthe mention was in a comment), the author, a short excerpt of the mentioning content,\nand `read_at`.\n\n`post_id` is always present, including for mentions in comments, so every row can be\ndeep-linked. `excerpt` is plain text: markup is stripped (a mention contributes its\nvisible `@Name`) and common entities are decoded, so it can be rendered as-is.\n","operationId":"BoldAdminWeb.Api.CommunityController.mentions","parameters":[{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}},{"description":"Page number (default: 1)","in":"query","name":"page","required":false,"schema":{"type":"integer"}},{"description":"Items per page (default: 20, max: 100)","in":"query","name":"page_size","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MentionsResponse"}}},"description":"Mentions list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized - X-Viewer-ID header required"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"List mentions of the current viewer","tags":["Community"]}},"/api/v1/viewers/{id}":{"delete":{"callbacks":{},"description":"Deletes an existing viewer.","operationId":"BoldAdminWeb.Api.ViewerController.delete","parameters":[{"description":"Viewer UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Viewer deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"Delete a viewer","tags":["Viewers"]},"get":{"callbacks":{},"description":"Returns a single viewer by ID.","operationId":"BoldAdminWeb.Api.ViewerController.show","parameters":[{"description":"Viewer UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerResponse"}}},"description":"Viewer details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"Get a viewer","tags":["Viewers"]},"patch":{"callbacks":{},"description":"Updates an existing viewer.","operationId":"BoldAdminWeb.Api.ViewerController.update (2)","parameters":[{"description":"Viewer UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerUpdate"}}},"description":"Viewer parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerResponse"}}},"description":"Viewer updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Update a viewer","tags":["Viewers"]},"put":{"callbacks":{},"description":"Updates an existing viewer.","operationId":"BoldAdminWeb.Api.ViewerController.update","parameters":[{"description":"Viewer UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerUpdate"}}},"description":"Viewer parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerResponse"}}},"description":"Viewer updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Update a viewer","tags":["Viewers"]}},"/api/v1/viewers/{viewer_id}/progress/{video_id}":{"delete":{"callbacks":{},"description":"Resets progress by deleting the progress record. This allows the viewer to start fresh on the video.","operationId":"BoldAdminWeb.Api.ProgressController.delete","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}},{"description":"Video UUID","in":"path","name":"video_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Progress reset"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Progress not found"}},"summary":"Reset progress","tags":["Progress"]},"get":{"callbacks":{},"description":"Returns progress for a specific viewer-video pair.","operationId":"BoldAdminWeb.Api.ProgressController.show","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}},{"description":"Video UUID","in":"path","name":"video_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgressResponse"}}},"description":"Progress details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Progress not found"}},"summary":"Get progress for a video","tags":["Progress"]},"post":{"callbacks":{},"description":"Creates or updates progress for a viewer-video pair. If progress exists, it will be updated; otherwise, a new record will be created.","operationId":"BoldAdminWeb.Api.ProgressController.upsert","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}},{"description":"Video UUID","in":"path","name":"video_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgressUpsert"}}},"description":"Progress parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgressResponse"}}},"description":"Progress updated"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgressResponse"}}},"description":"Progress created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer or video not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Create or update progress","tags":["Progress"]}},"/api/v1/admin/imports/{run_id}/cancel":{"post":{"callbacks":{},"description":"Cooperative cancellation: stops future BOLD start/chain jobs, but keeps polling and finalizing already-submitted importer batches. Those batches, including their importer-queued items, drain normally. Does not kill active transfers. status becomes canceled immediately; finished_at stays null until draining finishes. Repeated cancellation is safe.","operationId":"BoldAdminWeb.Api.Admin.ImportController.cancel","parameters":[{"description":"Run UUID","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImportResponse"}}},"description":"Run"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Run not found"}},"security":[{"admin_api_token":[]}],"summary":"Cancel future import dispatch","tags":["Admin Imports"]}},"/api/v1/viewers/{viewer_id}/notification-preferences":{"get":{"callbacks":{},"description":"Returns the viewer's notification channel switches as `{\"channels\": {\"email\": bool, \"push\": bool}}`. Both default to true. Requires the `viewers` feature.","operationId":"BoldAdminWeb.Api.ViewerNotificationPreferencesController.show","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}},"description":"Notification preferences"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"Get a viewer's notification preferences","tags":["Viewers"]},"patch":{"callbacks":{},"description":"Updates one or both notification channels via `{\"channels\": {\"email\": bool, \"push\": bool}}`. An absent channel is left unchanged. Requires the `viewers` feature.","operationId":"BoldAdminWeb.Api.ViewerNotificationPreferencesController.update","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesUpdate"}}},"description":"Notification preferences","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}},"description":"Updated notification preferences"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Update a viewer's notification preferences","tags":["Viewers"]}},"/api/v1/me":{"get":{"callbacks":{},"description":"Returns the account, key kind, non-secret key short ID, and account feature flags.\nSend `Authorization: Bearer <key>` for either a tenant or admin API key; callers\ndo not need to know the kind first. Admin keys may alternatively use\n`X-Bold-Admin-Api-Key`. If both headers are present, Authorization takes precedence.\nThis endpoint accepts persisted `bold_live_` API keys, not legacy signed tokens.\nMissing, invalid, revoked, expired keys and inactive accounts return 401.\nFeature flags describe account configuration, not a grant of admin permissions;\nviewer memory requires both viewers and viewer_memory. Existing endpoints keep\ntheir authentication requirements.\n","operationId":"BoldAdminWeb.Api.MeController.show","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Identity"}}},"description":"API key identity"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"}},"security":[{"api_token":[]},{"admin_api_token":[]}],"summary":"Identify an API key","tags":["Identity"]}},"/api/v1/admin/viewers/{viewer_id}/session-management-exemption":{"put":{"callbacks":{},"description":"Marks whether a viewer is exempt from device-limit enforcement. This is distinct from numeric device_limit_override.","operationId":"BoldAdminWeb.Api.Admin.ViewerController.update_session_management_exemption","parameters":[{"description":"Bold viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSessionManagementExemptionRequest"}}},"description":"Exemption value","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSessionManagementExemptionResponse"}}},"description":"Exemption updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid value"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Tenant migration unavailable"}},"security":[{"admin_api_token":[]}],"summary":"Set or clear a viewer's session-management exemption","tags":["Admin Viewers"]}},"/api/v1/session-management/viewers/by-external-id/{external_id}/device-limit":{"put":{"callbacks":{},"description":"Tenant-scoped customer-server endpoint. Mutates only the resolved viewer's device-limit override when per-user admin overrides are enabled for the account.","operationId":"BoldAdminWeb.Api.SessionManagement.ViewerController.update_device_limit","parameters":[{"description":"Optional tenant slug. When provided, it must match the tenant API key's account slug.","in":"header","name":"X-Bold-Tenant-Slug","required":false,"schema":{"type":"string"}},{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementDeviceLimitRequest"}}},"description":"Limit value","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementViewerStateResponse"}}},"description":"Viewer state"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Customer-admin overrides disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid limit"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Auth policy unavailable"}},"security":[{"api_token":[]}],"summary":"Set or clear a viewer's device-limit override by upstream user ID","tags":["Session Management"]}},"/api/v1/admin/videos":{"get":{"callbacks":{},"description":"Includes every privacy level, publication state and processing status. Ordered newest-created first, with page/page_size pagination in meta and Link headers. Filters combine with AND. Requires X-Bold-Admin-Api-Key; tenant keys are rejected.","operationId":"BoldAdminWeb.Api.Admin.VideoController.index","parameters":[{"description":"Page number","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Items per page","in":"query","name":"page_size","required":false,"schema":{"default":20,"minimum":1,"type":"integer"}},{"description":"Privacy level","in":"query","name":"privacy","required":false,"schema":{"enum":["public","unlisted","private"],"type":"string"}},{"description":"Exact processing status","in":"query","name":"status","required":false,"schema":{"description":"Stored video processing status; independent of transcript/index readiness","enum":["novideo","waiting","queued","uploading","transfer_scheduled","transfer_started","transferring","ready_for_processing","preparing","processing","zoom_processing","generating_ai_metadata","transcribed","ready","done","failed","import_failed","transfer_failed","encoding_error","errored","error"],"nullable":true,"type":"string"}},{"description":"Tag name or slug","in":"query","name":"tag","required":false,"schema":{"type":"string"}},{"description":"Collection UUID","in":"query","name":"collection","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Active video type UUID","in":"query","name":"type","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Exact imported external ID (imported_id)","in":"query","name":"external_id","required":false,"schema":{"type":"string"}},{"description":"Updated strictly after this ISO 8601 timestamp (normalized to UTC)","in":"query","name":"updated_since","required":false,"schema":{"format":"date-time","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideosResponse"}}},"description":"Video inventory"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid filters or pagination"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"}},"security":[{"admin_api_token":[]}],"summary":"List all account videos (admin)","tags":["Admin Videos"]},"post":{"callbacks":{},"description":"Requires X-Bold-Admin-Api-Key. Direct media uses file transfer; YouTube/Vimeo video URLs use the importer. Playlists/channels require BOLD-2021 runs and return 400. Poll GET /api/v1/admin/videos/:internal_id. Omitted/null published_at means unpublished. Only explicit tags are applied, not video-type default tags. Replays do not change metadata. Dedupe uses imported_url (lowercase host, default port and fragment removed; direct URL path/query preserved), canonical YouTube video IDs, or external_id for imported_from=api. Historical public ingestion did not store source URLs, so those records need external_id for dedupe.","operationId":"BoldAdminWeb.Api.Admin.VideoIngestController.create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoIngestRequest"}}},"description":"Import metadata","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoIngestResponse"}}},"description":"Existing video, unchanged"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoIngestResponse"}}},"description":"Video queued"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Playlist or channel unsupported"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid metadata"}},"security":[{"admin_api_token":[]}],"summary":"Import one video from a URL (admin)","tags":["Admin Videos"]}},"/api/v1/admin/viewers/{viewer_id}/sessions":{"get":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Admin.ViewerSessionController.index","parameters":[{"description":"Viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminViewerSessionsResponse"}}},"description":"Sessions for viewer"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"security":[{"admin_api_token":[]}],"summary":"List active + last-30-days revoked sessions for a viewer","tags":["Admin Auth Sessions"]}},"/api/v1/admin/community/posts/{post_id}/comments":{"post":{"callbacks":{},"description":"Creates a comment (or, with `parent_id`, a reply) on an existing post using\nthe same import parameters as post creation, `created_at` and `updated_at`\nincluded. Comments nest up to 2 levels.\n\nThe post's `comments_count` is incremented in the same transaction, exactly\nas for a natively-created comment, and so are any `mentions`.\n\n**Mentions recorded here never send a notification**, exactly as on post\ncreation.\n","operationId":"BoldAdminWeb.Api.Admin.CommunityController.create_comment","parameters":[{"description":"Post UUID","in":"path","name":"post_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCommunityCommentCreate"}}},"description":"Comment import parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityCommentResponse"}}},"description":"Comment already imported under this external_ref"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityCommentResponse"}}},"description":"Comment created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Post or parent comment not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Validation errors"}},"security":[{"admin_api_token":[]}],"summary":"Create a comment with import parameters","tags":["Admin Community"]}},"/api/v1/admin/collections/{id}/videos":{"delete":{"callbacks":{},"description":"Idempotently removes only the requested memberships. Other collections and videos are unchanged. Accepts UUIDs, hashids and slugs across all video states. Validates the entire batch before writing; unknown videos return 404 without changes. Counts include all members. Requires an admin key.","operationId":"BoldAdminWeb.Api.Admin.CollectionController.remove_videos","parameters":[{"description":"Collection UUID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionVideosRequest"}}},"description":"Videos to remove","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionVideosResponse"}}},"description":"Memberships removed"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid video_ids"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Collection or video not found"}},"security":[{"admin_api_token":[]}],"summary":"Remove videos from a collection (admin)","tags":["Admin Collections"]},"post":{"callbacks":{},"description":"Idempotently adds a batch without changing other memberships. Resolves UUIDs, hashids and slugs across all video states, including private videos. Validates the entire batch before writing; an unknown video returns 404 without changes. Response IDs are internal UUIDs; counts include all members.","operationId":"BoldAdminWeb.Api.Admin.CollectionController.add_videos","parameters":[{"description":"Collection UUID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionVideosRequest"}}},"description":"Videos to add","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionVideosResponse"}}},"description":"Memberships added"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid video_ids"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Collection or video not found"}},"security":[{"admin_api_token":[]}],"summary":"Add videos to a collection (admin)","tags":["Admin Collections"]}},"/api/v1/admin/videos/{id}":{"delete":{"callbacks":{},"description":"Deletes within the authenticated tenant and returns the last admin video state. Unknown videos and repeated deletes return 404. Queues removal from search/RAG indexes.","operationId":"BoldAdminWeb.Api.Admin.VideoController.delete","parameters":[{"description":"Video UUID, hashid or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoResponse"}}},"description":"Deleted video"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Video not found"}},"security":[{"admin_api_token":[]}],"summary":"Delete a video (admin)","tags":["Admin Videos"]},"get":{"callbacks":{},"description":"Includes private, unlisted, unpublished, processing and failed videos. Prefer internal_id (UUID); legacy hashids and slugs are also accepted. ready means a transcript_json track with a URL exists and stored RAG ingestion is completed. It does not guarantee index freshness or viewer access. failure_reason uses recorded status_detail for video/transcription failures, with a human-readable status fallback.","operationId":"BoldAdminWeb.Api.Admin.VideoController.show","parameters":[{"description":"Video internal UUID, hashid or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoResponse"}}},"description":"Video state"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Video not found"}},"security":[{"admin_api_token":[]}],"summary":"Get a video's full state (admin)","tags":["Admin Videos"]},"patch":{"callbacks":{},"description":"Partial update; omitted fields are unchanged. published_at accepts ISO 8601 with an offset or null. video_type accepts a system/account type UUID or slug. Tags are names; collections are tenant UUIDs. For tags and collections, replacement (if supplied) is applied first, then additions, then removals; removal wins. Tag names are trimmed and compared case-insensitively. Unknown fields return 400. Search and RAG synchronization is queued, not synchronous.","operationId":"BoldAdminWeb.Api.Admin.VideoController.update","parameters":[{"description":"Video UUID, hashid or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoUpdateRequest"}}},"description":"Metadata fields","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoResponse"}}},"description":"Updated video"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Video not found"}},"security":[{"admin_api_token":[]}],"summary":"Update video metadata (admin)","tags":["Admin Videos"]}},"/api/v1/search":{"get":{"callbacks":{},"description":"Searches public, published video transcript and indexed attachment content.\n\nThis endpoint is shared by public API clients and the hosted portal runtime.\nServer-side filters always restrict results to public videos published before the request time.\nUse `search_mode=preview` for typeahead; it never records an interaction.\nSettled searches return `interaction_id` with best-effort asynchronous capture.\nReuse a UUID `request_id` on retries of the same submitted action for analytics deduplication.\n","operationId":"BoldAdminWeb.Shared.SearchController.search (2)","parameters":[{"description":"Search query","in":"query","name":"query","required":false,"schema":{"type":"string"}},{"description":"Caller action UUID; reuse for retries. Invalid/missing keys create a new interaction.","in":"query","name":"request_id","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Preview requests never persist. Settle only on submit or result selection.","in":"query","name":"search_mode","required":false,"schema":{"default":"settled","enum":["preview","settled"],"type":"string"}},{"description":"Existing viewer UUID or external ID; never downgrade unresolved viewers to anonymous capture.","in":"query","name":"viewer","required":false,"schema":{"type":"string"}},{"description":"Optional analytics channel. Missing/unrecognized values become unknown and never reject search.","in":"query","name":"channel","required":false,"schema":{"enum":["portal","embed","api","mcp"],"type":"string"}},{"description":"Optional analytics client name: trimmed 1–128 characters, at most 512 UTF-8 bytes. Invalid values become unknown.","in":"query","name":"client_name","required":false,"schema":{"maxLength":128,"minLength":1,"type":"string"}},{"description":"Optional analytics client version: trimmed 1–128 characters, at most 512 UTF-8 bytes. Invalid values become unknown.","in":"query","name":"client_version","required":false,"schema":{"maxLength":128,"minLength":1,"type":"string"}},{"description":"Page number (default: 1)","in":"query","name":"page","required":false,"schema":{"type":"integer"}},{"description":"Results per page (default: 10)","in":"query","name":"per_page","required":false,"schema":{"type":"integer"}},{"description":"Comma-separated speaker labels","in":"query","name":"speakers","required":false,"schema":{"type":"string"}},{"description":"Filter by a single collection UUID","in":"query","name":"collection_id","required":false,"schema":{"type":"string"}},{"description":"Filter by multiple collection UUIDs (OR semantics). Supports repeated query params and comma-separated values.","explode":true,"in":"query","name":"collection_ids","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Only include content published after this ISO 8601 timestamp","in":"query","name":"published_after","required":false,"schema":{"type":"string"}},{"description":"Only include content published before this ISO 8601 timestamp","in":"query","name":"published_before","required":false,"schema":{"type":"string"}},{"description":"Meilisearch sort expression, such as published_at:desc","in":"query","name":"sort","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}},"description":"Search results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchErrorResponse"}}},"description":"Search failed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiAuthErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Search public video content","tags":["Search"]}},"/api/v1/ai/conversations":{"get":{"callbacks":{},"description":"Returns library conversations and video conversations whose published videos are accessible to this credential, in reverse-chronological order.","operationId":"BoldAdminWeb.Api.ConversationsController.index","parameters":[{"description":"Viewer UUID or external ID","in":"query","name":"viewer","required":true,"schema":{"type":"string"}},{"description":"Results per page (max 100)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Offset for pagination","in":"query","name":"offset","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnifiedConversationListResponse"}}},"description":"Conversation list"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing viewer parameter"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"List all conversations for a viewer","tags":["AI Conversations"]}},"/api/v1/ai/search":{"post":{"callbacks":{},"description":"Semantic search across your video library with AI-synthesized results.\n\nReturns relevant transcript segments and an AI-generated summary.\nSources include a fresh playback_token for signed videos, null for public or non-playback sources.\nFaster than full chat—use this for search experiences.\nUse `search_mode: \"preview\"` for typeahead (no capture, null interaction_id).\nSettled searches return `interaction_id` in JSON and `message_complete`.\nReuse a UUID `request_id` on retries for analytics deduplication, not response replay.\nCapture is best-effort and asynchronous; it never blocks the search response.\nOptional `channel`, `client_name`, and `client_version` identify the caller for\nanalytics. They are outside retry identity; invalid values become unknown and\nnever reject the search.\nError responses omit internal provider and exception details.\n\n**Streaming (default):** Returns SSE events:\n- `message_start`: Stream started\n- `sources`: Matching transcript segments\n- `text_delta`: Synthesis text chunk\n- `message_complete`: Complete response\n\nSet `stream=false` for a single JSON response.\n","operationId":"BoldAdminWeb.Api.AiSearchController.search","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSearchRequest"}}},"description":"Search request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiSearchResponse"}}},"description":"Search results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid query"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI not enabled"}},"summary":"Search with AI synthesis","tags":["AI Search"]}},"/api/v1/playlists":{"get":{"callbacks":{},"description":"Returns all playlists for your account.","operationId":"BoldAdminWeb.Api.PlaylistController.index (2)","parameters":[{"description":"Maximum number of playlists (1-1000, default: 50)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Offset for pagination (default: 0)","in":"query","name":"offset","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaylistsResponse"}}},"description":"Playlists"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"List playlists","tags":["Playlists"]}},"/api/v1/community/mentions/read":{"post":{"callbacks":{},"description":"Marks mentions of the `X-Viewer-ID` viewer as read. Send `{\"ids\": [...]}` to mark\nspecific mentions or `{\"all\": true}` to mark every unread one.\n\nOnly the calling viewer's own mentions are touched, so an id belonging to someone else\nis a no-op. Already-read mentions keep their original `read_at`.\n","operationId":"BoldAdminWeb.Api.CommunityController.mark_read","parameters":[{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MentionsReadRequest"}}},"description":"Mentions to mark read","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MentionsReadResponse"}}},"description":"Mentions marked read"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized - X-Viewer-ID header required"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Mark mentions read","tags":["Community"]}},"/api/v1/session-management/viewers/by-external-id/{external_id}/sessions/{id}/revoke":{"post":{"callbacks":{},"operationId":"BoldAdminWeb.Api.SessionManagement.ViewerSessionController.revoke","parameters":[{"description":"Optional tenant slug. When provided, it must match the tenant API key's account slug.","in":"header","name":"X-Bold-Tenant-Slug","required":false,"schema":{"type":"string"}},{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}},{"description":"Session UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementRevokeSessionRequest"}}},"description":"Optional reason","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementRevokeSessionResponse"}}},"description":"Session revoked"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid reason"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session management unavailable or account inactive"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer or session not found"}},"security":[{"api_token":[]}],"summary":"Revoke a viewer session by upstream user ID","tags":["Session Management"]}},"/api/v1/videos/{id}/transcript":{"get":{"callbacks":{},"description":"Returns transcript content, not a download URL. Resolves UUID, hashid, or slug with\nthe same access rules as GET /api/v1/videos/:id. Admin API keys supplied as Bearer\ntokens additionally allow private slug lookup within their account.\n\nSelect txt, srt, vtt, or json with `format` (default json). The query parameter\ntakes precedence over Accept, including an otherwise unsupported Accept header.\nJSON segments use seconds and retain speaker labels when present; language comes\nfrom transcript metadata or the text track. TXT separates utterances with blank\nlines. Subtitle cues use the existing word-timed subtitle generator.\n\nMissing or empty transcripts return 404 with code transcript_not_ready.\nStorage failures return 502, not a processing response.\n","operationId":"BoldAdminWeb.Api.VideoController.transcript","parameters":[{"description":"Video ID (hashid, UUID) or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Transcript format (overrides Accept)","in":"query","name":"format","required":false,"schema":{"default":"json","enum":["txt","srt","vtt","json"],"type":"string"}},{"description":"application/json, text/plain, application/x-subrip, or text/vtt","in":"header","name":"accept","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoTranscriptResponse"}},"application/x-subrip":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}},"text/vtt":{"schema":{"type":"string"}}},"description":"Transcript content"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unknown format"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video not found or transcript_not_ready"},"406":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unsupported Accept header"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Transcript storage unavailable"}},"summary":"Get a video's transcript","tags":["Videos"]}},"/api/v1/collections":{"get":{"callbacks":{},"description":"Returns collections for the current tenant. Use query params to filter.\n\nIf both `root_only` and `parent_id` are provided, `root_only` takes precedence.\n","operationId":"BoldAdminWeb.Api.CollectionController.index","parameters":[{"description":"Only return root-level collections (no parent)","in":"query","name":"root_only","required":false,"schema":{"type":"boolean"}},{"description":"Filter by parent collection UUID","in":"query","name":"parent_id","required":false,"schema":{"type":"string"}},{"description":"Maximum number of collections (1-1000, default: 50)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Offset for pagination (default: 0)","in":"query","name":"offset","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionsResponse"}}},"description":"Collections list"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parent_id format"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"List collections","tags":["Collections"]}},"/api/v1/admin/viewers/{viewer_id}/device-limit":{"put":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Admin.ViewerController.update_device_limit","parameters":[{"description":"Bold viewer UUID","in":"path","name":"viewer_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminDeviceLimitRequest"}}},"description":"Limit value","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminDeviceLimitResponse"}}},"description":"Override updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid limit"}},"security":[{"admin_api_token":[]}],"summary":"Set or clear a viewer's device-limit override","tags":["Admin Viewers"]}},"/api/v1/admin/auth/policy":{"get":{"callbacks":{},"description":"Returns max_devices_per_user, enforcement_mode/phase, session_ttl_days, allow_member_self_manage, admin_override_per_user, and auth challenge email branding fields. JWT-verification config (jwks_url, jwt_issuer, jwt_audience) is deliberately excluded.","operationId":"BoldAdminWeb.Api.Admin.PolicyController.show","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAuthPolicyResponse"}}},"description":"Policy"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"}},"security":[{"admin_api_token":[]}],"summary":"Get the tenant's user-tunable auth policy","tags":["Admin Auth Policy"]},"put":{"callbacks":{},"description":"Casts any provided user-tunable fields and preserves omitted values. Auth challenge email branding may be configured here, but arbitrary From-domain configuration is not supported. JWKS/issuer/audience are silently ignored if present in the request body — they are infra config edited via Mission Control, not over the API.","operationId":"BoldAdminWeb.Api.Admin.PolicyController.update","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAuthPolicyUpdateRequest"}}},"description":"User-tunable policy fields","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAuthPolicyResponse"}}},"description":"Updated policy"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation failed"}},"security":[{"admin_api_token":[]}],"summary":"Update the tenant's user-tunable auth policy","tags":["Admin Auth Policy"]}},"/api/v1/community/posts/{id}/react":{"post":{"callbacks":{},"description":"Toggles the current viewer's reaction on a post. If the viewer has already\nreacted, the reaction is removed. If not, a reaction is added.\n\nRequires the `X-Viewer-ID` header.\n","operationId":"BoldAdminWeb.Api.CommunityController.react_to_post","parameters":[{"description":"Post UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionResponse"}}},"description":"Reaction toggled"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Post not found"}},"summary":"Toggle reaction on a post","tags":["Community"]}},"/api/v1/community/comments/{id}/react":{"post":{"callbacks":{},"description":"Toggles the current viewer's reaction on a comment. If the viewer has already\nreacted, the reaction is removed. If not, a reaction is added.\n\nRequires the `X-Viewer-ID` header.\nDeleted comment placeholders cannot be reacted to and return 404.\n","operationId":"BoldAdminWeb.Api.CommunityController.react_to_comment","parameters":[{"description":"Comment UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionResponse"}}},"description":"Reaction toggled"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Comment not found"}},"summary":"Toggle reaction on a comment","tags":["Community"]}},"/api/v1/ai/voice/sessions":{"post":{"callbacks":{},"description":"Starts a real-time spoken conversation about one accessible, published video, grounded in its transcript.\n\nExisting credentials access public videos only. Private published lessons require\na separately issued server-only key with private_video_ai permission and a customer\nserver entitlement check. Viewer IDs do not grant access. Unlisted, unpublished\nand non-ready videos return 404 for every credential, matching text chat.\n\nSend the browser's WebRTC SDP offer and apply the returned `sdp` answer as the\npeer connection's remote description. Bold chooses the model, voice, and\ninstructions, and the browser's data channel is limited to mute, unmute,\ncontext appends, and close. End the call client-side after `max_seconds`, or\nafter `idle_seconds` of silence.\n\nVoice must also be enabled in the account's AI Settings. Turning it off\nrefuses new sessions with `403 feature_disabled`. Voice, persona and limits\nare resolved when a session starts; changes do not affect a running session.\n\nThe complete instructions have a 54,000-character budget, including personality,\nviewer context and grounding rules. Transcripts that exceed the remaining space\nare sampled evenly across the whole video so the session can still name late moments.\n\nEach account has a voice-minute allowance for the UTC calendar month. Once it is\nused up, new sessions are refused with `402 voice_allowance_exceeded` and the\nday the allowance resets; sessions already running are unaffected.\nOpen calls reserve their full maximum duration. The last admitted call may\nexceed the remaining allowance; this is not an exact spending ceiling.\n\nA viewer holds one session at a time. Starting a second one for the same\nviewer is refused with `409 voice_session_limit`; end the first session, or\nwait for it to end on its own, before starting another.\nOmitted viewers share one anonymous slot per account. Unconfirmed shutdowns\nretain their slot and reservation until cleanup succeeds.\n","operationId":"BoldAdminWeb.Api.VoiceController.create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceSessionRequest"}}},"description":"Voice session request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceSessionResponse"}}},"description":"Voice session started"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing video ID or invalid SDP offer"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceAllowanceError"}}},"description":"The account is out of voice minutes for this month"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI or voice not enabled for this account"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video, viewer, or transcript not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"This viewer already has an active voice session"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Too many voice sessions started; see the retry-after header"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The voice session couldn't start (retryable)"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Voice sessions are not configured"}},"summary":"Start a voice session about a video","tags":["AI Voice"]}},"/api/v1/videos/latest":{"get":{"callbacks":{},"description":"Returns the most recently published public videos.\nSigned playback includes Mux playback and storyboard tokens; public playback tokens are null.\n\nWhen `viewer_id` is provided, each video includes a `progress` field showing the viewer's\nwatch progress (current_time, duration, percentage, completed status). Videos without\nprogress for that viewer will have `progress: null`.\n","operationId":"BoldAdminWeb.Api.VideoController.latest","parameters":[{"description":"Maximum number of videos (default: 6, max: 50)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Filter by tag","in":"query","name":"tag","required":false,"schema":{"type":"string"}},{"description":"Filter by collection ID","in":"query","name":"collection_id","required":false,"schema":{"type":"string"}},{"description":"Filter by multiple collection IDs (OR semantics). Supports repeated query params and comma-separated values.","explode":true,"in":"query","name":"collection_ids","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"},{"description":"Viewer UUID to include watch progress","in":"query","name":"viewer_id","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosResponse"}}},"description":"Videos list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Get latest videos","tags":["Videos"]}},"/api/v1/collections/{id}":{"get":{"callbacks":{},"description":"Returns a single collection by ID or slug.","operationId":"BoldAdminWeb.Api.CollectionController.show","parameters":[{"description":"Collection UUID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Include 'children' for direct children or 'descendants' for full tree","in":"query","name":"include","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponse"}}},"description":"Collection details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Collection not found"}},"summary":"Get a collection","tags":["Collections"]}},"/api/v1/conversation-starters":{"get":{"callbacks":{},"description":"Resolves the conversation-starter pool for a set of collections, falling back to\nthe account-level list for any collection with none of its own. Results are\nunioned and deduped on exact text across the requested collections; each entry\nis tagged with its `source` (`collection` or `account`) and `collection_id`\n(`null` for account-sourced starters).\n\nWith no `collection_ids`, returns the account-level starters only.\n\nUnknown or non-UUID ids are silently ignored — same lenient posture as other\ncollection-scoped endpoints.\n","operationId":"BoldAdminWeb.Api.ConversationStartersController.index","parameters":[{"description":"Collection UUIDs — repeat the param (`?collection_ids=a&collection_ids=b`) or pass a comma-separated string. Unknown ids are ignored.","explode":true,"in":"query","name":"collection_ids","required":false,"schema":{"items":{"type":"string"},"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationStartersResponse"}}},"description":"Resolved conversation starters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Resolve conversation starters","tags":["Collections"]}},"/api/v1/admin/videos/{id}/unpublish":{"post":{"callbacks":{},"description":"Sets privacy to unlisted, preserving published_at and all other metadata, including is_published. This removes the video from public lists; it does not revoke existing playback links. No body fields accepted. Queues search/RAG sync.","operationId":"BoldAdminWeb.Api.Admin.VideoController.unpublish","parameters":[{"description":"Video UUID, hashid or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoResponse"}}},"description":"Unpublished video"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Video not found"}},"security":[{"admin_api_token":[]}],"summary":"Unpublish a video (admin)","tags":["Admin Videos"]}},"/api/v1/playlists/{id}":{"get":{"callbacks":{},"description":"Returns a playlist with its videos.","operationId":"BoldAdminWeb.Api.PlaylistController.show","parameters":[{"description":"Playlist ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaylistResponse"}}},"description":"Playlist"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Playlist not found"}},"summary":"Get a playlist","tags":["Playlists"]}},"/api/v1/ai/videos/{id}/chat":{"get":{"callbacks":{},"description":"Retrieve paginated conversation history for an accessible, published video.","operationId":"BoldAdminWeb.Api.AiController.index","parameters":[{"description":"Video ID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Max results (default 20, max 100)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Pagination offset (default 0)","in":"query","name":"offset","required":false,"schema":{"type":"integer"}},{"description":"Viewer UUID or external ID","in":"query","name":"viewer","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoConversationListResponse"}}},"description":"Conversation list"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing viewer parameter"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI feature not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video or viewer not found"}},"summary":"List video chat conversations","tags":["AI Chat"]},"post":{"callbacks":{},"description":"Ask questions about a specific video using its transcript.\n\nRequires a public, published video with status ready or done. Private published\nlessons require a separately issued server-only key with private_video_ai permission\nand an entitlement check by the customer server. Viewer IDs are not authorization.\nUnlisted, unpublished and processing videos return 404 for every credential.\n\nSet `stream=false` to receive a JSON response instead of SSE stream.\nJSON and `message_complete` include `interaction_id` for this turn.\nReuse a UUID `request_id` on retries for analytics deduplication, not response replay.\nCapture is best-effort and asynchronous; the ID is not a persistence acknowledgement.\nOptional `channel`, `client_name`, and `client_version` describe the caller for\nanalytics and are outside retry identity. Invalid metadata becomes unknown and\nnever rejects an AI request.\n\n**Streaming (default)**: Returns `text/event-stream` with events:\n- `message_start`: Initial event with conversation_id\n- `text_delta`: Incremental text chunks\n- `message_complete`: Final event with full content and metadata\n- `error`: Error event if something goes wrong\n","operationId":"BoldAdminWeb.Api.AiController.ask","parameters":[{"description":"Video ID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiChatRequest"}}},"description":"Chat request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiChatResponse"}}},"description":"Chat response (JSON when stream=false)"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid request"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI feature not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video, transcript, or viewer not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Processing failed"}},"summary":"Chat about a video","tags":["AI Chat"]}},"/api/v1/session-management/viewers/by-external-id/{external_id}/sessions/revoke-all":{"post":{"callbacks":{},"operationId":"BoldAdminWeb.Api.SessionManagement.ViewerSessionController.revoke_all","parameters":[{"description":"Optional tenant slug. When provided, it must match the tenant API key's account slug.","in":"header","name":"X-Bold-Tenant-Slug","required":false,"schema":{"type":"string"}},{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementRevokeSessionRequest"}}},"description":"Optional reason","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementRevokeAllResponse"}}},"description":"All sessions revoked"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid reason"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session management unavailable or account inactive"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"}},"security":[{"api_token":[]}],"summary":"Revoke every active session for a viewer by upstream user ID","tags":["Session Management"]}},"/api/v1/auth/sessions/{id}/revoke":{"post":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Auth.SessionController.revoke","parameters":[{"description":"Session UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeAuthSessionResponse"}}},"description":"Session revoked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Self-management disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session not found or not owned by viewer"}},"security":[{"upstream_jwt":[]}],"summary":"Self-revoke one of the authenticated viewer's sessions","tags":["Auth Sessions"]}},"/api/v1/auth/sessions/{id}/verify":{"get":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Auth.SessionController.verify","parameters":[{"description":"Session UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAuthSessionResponse"}}},"description":"Verification result"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Ownership mismatch or account inactive"}},"security":[{"upstream_jwt":[]}],"summary":"Verify a session is still valid","tags":["Auth Sessions"]}},"/api/v1/videos/{id}":{"get":{"callbacks":{},"description":"Returns a single video by hashid, UUID, or slug.\nSigned playback includes tokenized stream/download URLs, playback_token, storyboard_token,\nand playback_token_expires_at. Public playback tokens and expiration are null.\nPlayback credentials depend on playback policy and a nonempty Mux playback ID, not video visibility.\nRe-fetch this video for fresh tokens; no refresh endpoint is needed.\n\n**Lookup order:** UUID → hashid → slug.\n","operationId":"BoldAdminWeb.Api.VideoController.show (2)","parameters":[{"description":"Video ID (hashid, UUID) or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoResponse"}}},"description":"Video details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video not found"}},"summary":"Get a video","tags":["Videos"]}},"/api/v1/admin/videos/{id}/publish":{"post":{"callbacks":{},"description":"Sets privacy to public. Uses optional published_at (ISO 8601 or null), otherwise the stored timestamp. A null or future timestamp becomes now; a past timestamp is preserved. Does not change processing status or is_published. Public lists still exclude zoom_processing/import_failed. Queues search/RAG sync.","operationId":"BoldAdminWeb.Api.Admin.VideoController.publish","parameters":[{"description":"Video UUID, hashid or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoPublishRequest"}}},"description":"Optional publication timestamp","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVideoResponse"}}},"description":"Published video"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Video not found"}},"security":[{"admin_api_token":[]}],"summary":"Publish a video (admin)","tags":["Admin Videos"]}},"/api/v1/session-management/viewers/by-external-id/{external_id}/session-management-exemption":{"put":{"callbacks":{},"description":"Tenant-scoped customer-server endpoint. Marks whether the resolved viewer is exempt from session-management enforcement when per-user admin overrides are enabled.","operationId":"BoldAdminWeb.Api.SessionManagement.ViewerController.update_session_management_exemption","parameters":[{"description":"Optional tenant slug. When provided, it must match the tenant API key's account slug.","in":"header","name":"X-Bold-Tenant-Slug","required":false,"schema":{"type":"string"}},{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementExemptionRequest"}}},"description":"Exemption value","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementViewerStateResponse"}}},"description":"Viewer state"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Customer-admin overrides disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid value"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Auth policy or tenant migration unavailable"}},"security":[{"api_token":[]}],"summary":"Set or clear a viewer's session-management exemption by upstream user ID","tags":["Session Management"]}},"/api/v1/auth/challenges/{id}/resend":{"post":{"callbacks":{},"description":"Generates and emails a fresh verification code for an active challenge. The original challenge expiry is preserved and only the latest emailed code is valid.","operationId":"BoldAdminWeb.Api.Auth.ChallengeController.resend","parameters":[{"description":"Challenge UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeResendResponse"}}},"description":"Challenge code resent"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Invalid, expired, or resend-limited challenge"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Challenge ownership mismatch"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Viewer not provisioned"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Viewer email required"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Resend rate limited"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthChallengeError"}}},"description":"Challenge email delivery unavailable"}},"security":[{"upstream_jwt":[]}],"summary":"Resend an auth challenge code","tags":["Auth Challenges"]}},"/api/v1/admin/imports":{"get":{"callbacks":{},"description":"Admin-created runs only, newest first. Retained with their Oban jobs; independent of the Mission Control tray's short display window.","operationId":"BoldAdminWeb.Api.Admin.ImportController.index","parameters":[{"description":"Page number","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Runs per page","in":"query","name":"page_size","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImportsResponse"}}},"description":"Runs"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid pagination"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"}},"security":[{"admin_api_token":[]}],"summary":"List recent admin import runs","tags":["Admin Imports"]},"post":{"callbacks":{},"description":"Imports a playlist, channel or single video using the existing importer. Active retries of the same trimmed source URL return the existing run without changing its options. Managed YouTube installations require Mission Control preflight and return 422; this endpoint never bypasses preflight or accepts cookies. Requires X-Bold-Admin-Api-Key; tenant keys are rejected.","operationId":"BoldAdminWeb.Api.Admin.ImportController.create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImportRequest"}}},"description":"Import options","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImportResponse"}}},"description":"Existing active run"},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImportResponse"}}},"description":"Run queued"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid input or preflight required"}},"security":[{"admin_api_token":[]}],"summary":"Start a YouTube import run (admin)","tags":["Admin Imports"]}},"/api/v1/ai/videos/{id}/chat/{conversation_id}":{"delete":{"callbacks":{},"description":"Delete a video chat conversation and all associated messages when its published video is accessible.","operationId":"BoldAdminWeb.Api.AiController.delete","parameters":[{"description":"Video ID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Conversation ID","in":"path","name":"conversation_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Conversation deleted"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conversation/video mismatch"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI feature not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video or conversation not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deletion failed"}},"summary":"Delete a video chat conversation","tags":["AI Chat"]},"get":{"callbacks":{},"description":"Retrieve the full message history for an accessible, published video chat conversation. Each assistant message includes its exact persisted interaction_id when available; otherwise null.","operationId":"BoldAdminWeb.Api.AiController.show","parameters":[{"description":"Video ID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Conversation ID","in":"path","name":"conversation_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoConversationResponse"}}},"description":"Conversation"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conversation/video mismatch"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI feature not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video or conversation not found"}},"summary":"Get a video chat conversation","tags":["AI Chat"]},"post":{"callbacks":{},"description":"Continue an existing conversation about a video, rechecking the same published-video access boundary as a new chat. JSON and message_complete include interaction_id. Reuse request_id UUID on retries for best-effort analytics deduplication; this does not replay responses. Optional channel/client metadata is outside retry identity; invalid values become unknown and never reject the request.","operationId":"BoldAdminWeb.Api.AiController.continue_chat","parameters":[{"description":"Video ID or slug","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Existing conversation ID","in":"path","name":"conversation_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiChatRequest"}}},"description":"Chat request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiChatResponse"}}},"description":"Chat response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid request or conversation mismatch"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI feature not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Video, transcript, or conversation not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Processing failed"}},"summary":"Continue a video chat conversation","tags":["AI Chat"]}},"/api/v1/session-management/viewers/by-external-id/{external_id}":{"get":{"callbacks":{},"description":"Tenant-scoped customer-server endpoint. The normal BOLD tenant API key determines the account; no admin API key is required.","operationId":"BoldAdminWeb.Api.SessionManagement.ViewerController.by_external_id","parameters":[{"description":"Optional tenant slug. When provided, it must match the tenant API key's account slug.","in":"header","name":"X-Bold-Tenant-Slug","required":false,"schema":{"type":"string"}},{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementViewerResolveResponse"}}},"description":"Resolved viewer"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session management unavailable or account inactive"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Auth policy unavailable"}},"security":[{"api_token":[]}],"summary":"Resolve an upstream user ID to a BOLD viewer","tags":["Session Management"]}},"/api/v1/admin/images":{"post":{"callbacks":{},"description":"Uploads an image and stores it in Bold's public bucket, returning a stable\n`uploads.eu1.boldvideo.io` URL.\n\nThe file is sniffed by magic bytes against an allowlist (jpeg, png, gif, webp,\navif) — the client-declared content type is never trusted. SVG is rejected.\nMaximum file size is 5 MB.\n","operationId":"BoldAdminWeb.Api.Admin.ImageController.create","parameters":[],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/ImageUploadRequest"}}},"description":"Image file","required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageResponse"}}},"description":"Image uploaded"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unsupported media type or missing file"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"File too large (max 5 MB)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Upload failed"}},"security":[{"admin_api_token":[]}],"summary":"Upload an image","tags":["Admin Images"]}},"/api/v1/admin/collections":{"post":{"callbacks":{},"description":"Creates a collection, or returns the existing collection with the exact same name in this account (200, existing: true), without updating it. No description or privacy field. Requires X-Bold-Admin-Api-Key; tenant keys are rejected.","operationId":"BoldAdminWeb.Api.Admin.CollectionController.create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionCreateRequest"}}},"description":"Collection fields","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionResponse"}}},"description":"Collection already exists"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCollectionResponse"}}},"description":"Collection created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid collection"}},"security":[{"admin_api_token":[]}],"summary":"Create a collection (admin)","tags":["Admin Collections"]}},"/api/v1/community/posts":{"get":{"callbacks":{},"description":"Returns community posts for the current tenant with optional filtering and pagination.\n\nPosts are ordered by: pinned first, then by pin timestamp, then by creation date.\n\nPass the `X-Viewer-ID` header to include `viewer_has_reacted` boolean in each post's reactions.\n","operationId":"BoldAdminWeb.Api.CommunityController.index","parameters":[{"description":"Filter by category","example":"announcements","in":"query","name":"category","required":false,"schema":{"type":"string"}},{"description":"Page number (default: 1)","example":1,"in":"query","name":"page","required":false,"schema":{"type":"integer"}},{"description":"Items per page (default: 20, max: 100)","example":20,"in":"query","name":"page_size","required":false,"schema":{"type":"integer"}},{"description":"Viewer UUID for reaction status","in":"header","name":"X-Viewer-ID","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostsResponse"}}},"description":"Community posts list"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"List community posts","tags":["Community"]},"post":{"callbacks":{},"description":"Creates a new community post. Requires the `X-Viewer-ID` header to identify the author.\n\nThe viewer must exist in the system before creating a post. Pin state is\nprivileged and ignored on this route; the author comes from X-Viewer-ID.\n\nPass `mentions` — an array of **customer external ids**, never Bold viewer ids — to\nrecord mentions. Bold resolves each id to a viewer, creating one it has never seen,\nand stores the mention rows in the same transaction as the post. The array is capped\nat 25 ids, duplicates collapse, and a self-mention is dropped. Any id Bold could not\nrecord comes back in `mentions.skipped`; a bad id never fails the post.\n","operationId":"BoldAdminWeb.Api.CommunityController.create","parameters":[{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostCreate"}}},"description":"Post parameters","required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Post created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized - X-Viewer-ID header required"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Create a community post","tags":["Community"]}},"/api/v1/admin/community/posts":{"post":{"callbacks":{},"description":"Creates a community post on behalf of a member, preserving the original\nposting time, engagement count, and pinned state. Intended for migrating an\nexisting community into Bold.\n\n`created_at` sets both timestamps, so an imported post reads as written once\nand never edited. Send `updated_at` as well when the source post really was\nedited later; it requires `created_at` and may not precede it.\n\nThe author is resolved by `author.external_id`: an existing viewer with that\nid is reused untouched, otherwise one is created from the supplied name,\nemail and avatar (falling back to the external id for the name).\n\nSupplying `external_ref` makes the call idempotent — replaying the same\nreference returns **200** with the previously stored post instead of\ncreating a second one.\n\n`mentions` takes customer external ids and behaves exactly as on the public\nroute: resolved (or created), capped at 25, deduped, self-mention dropped,\nwith anything unrecorded returned in `mentions.skipped`.\n\n**Mentions recorded here never send a notification.** The rows are written,\nso the mention inbox and unread count are correct the first time the member\nopens the app, but no email or push is delivered — importing a backlog must\nnot notify a member about a two-year-old conversation.\n","operationId":"BoldAdminWeb.Api.Admin.CommunityController.create_post","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCommunityPostCreate"}}},"description":"Post import parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Post already imported under this external_ref"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Post created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Validation errors"}},"security":[{"admin_api_token":[]}],"summary":"Create a community post with import parameters","tags":["Admin Community"]}},"/api/v1/session-management/viewers/by-external-id/{external_id}/sessions":{"get":{"callbacks":{},"description":"Tenant-scoped customer-server endpoint. Returns the resolved viewer's computed session-management state plus active and recently revoked sessions.","operationId":"BoldAdminWeb.Api.SessionManagement.ViewerSessionController.index","parameters":[{"description":"Optional tenant slug. When provided, it must match the tenant API key's account slug.","in":"header","name":"X-Bold-Tenant-Slug","required":false,"schema":{"type":"string"}},{"description":"Upstream IdP user ID","in":"path","name":"external_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionManagementViewerSessionsResponse"}}},"description":"Sessions for viewer"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session management unavailable or account inactive"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Viewer not found"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Auth policy unavailable"}},"security":[{"api_token":[]}],"summary":"List sessions for a viewer by upstream user ID","tags":["Session Management"]}},"/api/v1/community/posts/{id}":{"delete":{"callbacks":{},"description":"Deletes an existing community post. Requires the `X-Viewer-ID` header.\n\nOnly the post author or an admin viewer can delete a post.\n","operationId":"BoldAdminWeb.Api.CommunityController.delete","parameters":[{"description":"Post UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{}},"description":"Post deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden - not post owner or admin"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Post not found"}},"summary":"Delete a community post","tags":["Community"]},"get":{"callbacks":{},"description":"Returns a single community post by ID with nested comments.\n\nPass the `X-Viewer-ID` header to include `viewer_has_reacted` booleans in the response.\n","operationId":"BoldAdminWeb.Api.CommunityController.show","parameters":[{"description":"Post UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID for reaction status","in":"header","name":"X-Viewer-ID","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Community post details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Post not found"}},"summary":"Get a community post","tags":["Community"]},"patch":{"callbacks":{},"description":"Updates an existing community post. Requires the `X-Viewer-ID` header.\n\nOnly the post author or an admin viewer can update a post. Only content and\ncategory can be changed; ownership, pin state, timestamps and counters are ignored.\n\nContent changes set edited_at. Pass mentions as customer external ids to record\nnewly mentioned members. Each member is notified at most once per post; repeated\nsaves preserve existing mention read state. Omitted mentions leave records unchanged.\n","operationId":"BoldAdminWeb.Api.CommunityController.update (2)","parameters":[{"description":"Post UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostUpdate"}}},"description":"Editable post parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Post updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden - not post owner or admin"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Post not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Update a community post","tags":["Community"]},"put":{"callbacks":{},"description":"Updates an existing community post. Requires the `X-Viewer-ID` header.\n\nOnly the post author or an admin viewer can update a post. Only content and\ncategory can be changed; ownership, pin state, timestamps and counters are ignored.\n\nContent changes set edited_at. Pass mentions as customer external ids to record\nnewly mentioned members. Each member is notified at most once per post; repeated\nsaves preserve existing mention read state. Omitted mentions leave records unchanged.\n","operationId":"BoldAdminWeb.Api.CommunityController.update","parameters":[{"description":"Post UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostUpdate"}}},"description":"Editable post parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Post updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden - not post owner or admin"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Post not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors"}},"summary":"Update a community post","tags":["Community"]}},"/api/v1/auth/sessions":{"get":{"callbacks":{},"operationId":"BoldAdminWeb.Api.Auth.SessionController.index","parameters":[{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSessionsListResponse"}}},"description":"Active sessions"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfManagementDisabledError"}}},"description":"Self-management disabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not provisioned"}},"security":[{"upstream_jwt":[]}],"summary":"List active sessions for the authenticated viewer","tags":["Auth Sessions"]},"post":{"callbacks":{},"description":"Verifies the upstream IdP bearer JWT, resolves the viewer, runs device-limit enforcement per the tenant's auth_policies row. Captures a coarse session location from the connecting IP; trusted server-side integrators may pass `client_ip` to geo-locate the real end user instead of their server's egress IP.","operationId":"BoldAdminWeb.Api.Auth.SessionController.create","parameters":[{"description":"Tenant slug for the BOLD account, for example `hrtu`.","in":"header","name":"X-Bold-Tenant-Slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAuthSessionRequest"}}},"description":"Create-session payload","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSessionResponse"}}},"description":"Session created or bypassed"},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthSessionChallengeResponse"}}},"description":"Challenge required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid bearer JWT"},"403":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/DeviceLimitError"},{"$ref":"#/components/schemas/Error"}]}}},"description":"Device limit exceeded or account inactive"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not provisioned"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer email required for challenge delivery"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Too many verification emails requested; retry after 15 minutes","headers":{"Retry-After":{"description":"Seconds to wait before requesting another verification email","required":true,"schema":{"enum":[900],"type":"integer"},"style":"simple"}}},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Challenge delivery unavailable"}},"security":[{"upstream_jwt":[]}],"summary":"Create a session for the authenticated viewer's device","tags":["Auth Sessions"]}},"/api/v1/settings":{"get":{"callbacks":{},"description":"Returns public configuration for your video portal.\n\nIncludes branding (logos, colors), AI settings, and portal layout configuration.\nUse this to customize embedded players or build custom portal UIs.\n\n`data.account.voice.enabled` indicates whether AI and Voice access and the\naccount's voice switch in AI Settings are enabled. Use it to show or hide voice controls; default to false if\nabsent. This read-only capability is not authorization: every new voice session\nchecks access and settings again and returns 403 `feature_disabled` if either is off.\nDisabling Voice prevents new sessions but does not end an existing call.\n","operationId":"BoldAdminWeb.Api.ProjectSettingsController.settings","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectSettings"}}},"description":"Settings"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"}},"summary":"Get portal settings","tags":["Settings"]}},"/api/v1/ai/chat":{"get":{"callbacks":{},"description":"Retrieve paginated conversation history for library-scoped AI chat.","operationId":"BoldAdminWeb.Api.AskStreamController.index","parameters":[{"description":"Max results (default 20, max 100)","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Pagination offset (default 0)","in":"query","name":"offset","required":false,"schema":{"type":"integer"}},{"description":"Viewer UUID or external ID","in":"query","name":"viewer","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryConversationListResponse"}}},"description":"Conversation list"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid profile_id"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — account inactive (subscription expired or no active plan) or required feature disabled"}},"summary":"List library chat conversations","tags":["AI Chat"]},"post":{"callbacks":{},"description":"Ask questions across your entire video library.\nVideo sources include a fresh playback_token for signed playback; public and non-playback sources use null.\n\nThe AI searches all transcripts, finds relevant content, and synthesizes an answer with citations.\nFactual name lookups use exact lexical recovery across profiles and transcripts. If factual\nretrieval is empty or weak, the response describes what the search could not find rather than asserting absence.\nUse `conversation_id` from the response to ask follow-up questions.\nJSON and `message_complete` include a turn-specific `interaction_id`.\nReuse a UUID `request_id` on retries for analytics deduplication, not response replay.\nCapture is best-effort and asynchronous; the ID is not a persistence acknowledgement.\nOptional `channel`, `client_name`, and `client_version` are analytics metadata,\noutside retry identity. Invalid metadata becomes unknown and never rejects AI.\nError responses omit internal provider and exception details.\n\nSet `synthesize=false` to stop after retrieval and return ordered evidence with\n`response_type: \"retrieval\"`. This mode persists the user query but no assistant\nanswer. Evidence IDs are deterministic within the ranked response, not durable\ndatabase IDs. Callers that synthesize answers elsewhere must send standalone\nfollow-up prompts because those external answers are not part of Bold's history.\n\n**Streaming (default):** Returns SSE events:\n- `message_start`: Stream started\n- `conversation_created`: Conversation record created\n- `image_analysis`: Uploaded images analyzed\n- `progress`: Pipeline stage status (e.g. \"Searching your library…\")\n- `sources`: Search results found\n- `citation_map`: Stable citation map for streamed text\n- `text_delta`: Raw answer text chunk (pre-finalization, see below)\n- `answer`: Complete finalized answer with citations and public-safe retrieval metadata\n- `message_complete`: Stream finished (carries the same finalized `content`)\n\nWith `synthesize=false`, `message_complete` is the only terminal success event.\nIt contains `response_type: \"retrieval\"`, `sources`, `confidence`, and retrieval\n`metadata`; it omits answer content, citations, and synthesis usage.\n\n**Answer finalization:** `text_delta` events stream raw model tokens. The final\nanswer text is then validated: any marker-delimited claim carrying an unknown\ncitation marker is removed (fail closed), and exact titles of cited videos are\nrewritten as Markdown links to the video's canonical portal URL. The terminal `answer` event\n(and `message_complete.content`) carry this finalized text — it is authoritative\nand is what gets persisted to the conversation. Clients MUST replace their\naccumulated `text_delta` buffer with `answer.content` on receipt; the finalized\ntext can differ from the concatenated deltas.\n\nAnswer responses include public-safe retrieval metadata: `retrieval_mode`, `degraded`,\nand `degraded_reason`. When retrieval recovers after Stage 1 exhaustion,\n`retrieval_mode` is `degraded` and `degraded_reason` is `stage1_exhausted`.\n\nSet `stream=false` for a single JSON response whose `content` is the same\nfinalized answer text, with the same retrieval metadata.\n","operationId":"BoldAdminWeb.Api.AskStreamController.ask","parameters":[{"description":"Set to \"true\" to bypass caching for this request. Requires allow_per_request_model to be enabled on the account.","in":"header","name":"X-Bold-Cache-Bypass","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryAiChatRequest"}}},"description":"Chat request","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LibraryAiChatResponse"}},"text/event-stream":{"schema":{"$ref":"#/components/schemas/AiStreamEvent"}}},"description":"Chat response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid request"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"AI not enabled"}},"summary":"Chat with your video library","tags":["AI Chat"]}},"/api/v1/viewers/lookup":{"get":{"callbacks":{},"description":"Finds a viewer by external_id or email. At least one query parameter must be provided.","operationId":"BoldAdminWeb.Api.ViewerController.lookup","parameters":[{"description":"External ID from course platform","in":"query","name":"external_id","required":false,"schema":{"type":"string"}},{"description":"Viewer email","in":"query","name":"email","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewerResponse"}}},"description":"Viewer found"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing query parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive, or the viewers feature is not enabled"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Viewer not found"}},"summary":"Look up a viewer","tags":["Viewers"]}},"/api/v1/community/posts/{post_id}/comments":{"post":{"callbacks":{},"description":"Creates a new comment on a post. Requires the `X-Viewer-ID` header.\n\nTo create a reply to another comment, include `parent_id` in the request body.\nComments can be nested up to 2 levels deep.\n\nPass `mentions` — an array of **customer external ids** — to record mentions, with the\nsame rules as post creation: capped at 25, deduped, self-mentions dropped, and any id\nBold could not record returned in `mentions.skipped`.\n","operationId":"BoldAdminWeb.Api.CommunityController.create_comment","parameters":[{"description":"Post UUID","in":"path","name":"post_id","required":true,"schema":{"type":"string"}},{"description":"Viewer UUID (required)","in":"header","name":"X-Viewer-ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityCommentCreate"}}},"description":"Comment parameters","required":false},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityCommentResponse"}}},"description":"Comment created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid parameters"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Account inactive — subscription expired or no active plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Post not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation errors (e.g., max depth exceeded)"}},"summary":"Create a comment on a post","tags":["Community"]}},"/api/v1/admin/community/comments/{id}":{"patch":{"callbacks":{},"description":"The comment-shaped mirror of the post repair route: corrects a comment's\n`content`, `created_at`, or `updated_at`. The author, the thread the\ncomment belongs to, the counters, and `external_ref` are untouched, even if\nthe request body carries them. Comments have no `category`.\n\nAs on posts, replaying a `POST` with a known `external_ref` is a pure read,\nso this `PATCH` is the only way to correct an imported comment; and an\nupdate that does not carry `updated_at` stamps it to now.\n\nNever notifies, same as everything else on this surface.\nDeleted comment placeholders cannot be repaired and return 404.\n","operationId":"BoldAdminWeb.Api.Admin.CommunityController.update_comment","parameters":[{"description":"Comment UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCommunityCommentUpdate"}}},"description":"Comment repair parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityCommentResponse"}}},"description":"Comment updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Comment not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Validation errors"}},"security":[{"admin_api_token":[]}],"summary":"Repair an imported community comment","tags":["Admin Community"]}},"/api/v1/admin/community/posts/{id}":{"patch":{"callbacks":{},"description":"Corrects a post's `content`, `category`, `created_at`, or `updated_at`. The\nauthor, the counters, and `external_ref` are untouched, even if the request\nbody carries them.\n\nThis is a **different verb** from `external_ref` replay: replaying a\n`POST` with a known `external_ref` stays a pure read of the stored row.\nThis `PATCH` is the only way to correct a botched import without deleting\nand re-importing the post — and a corrected `created_at` moves the post to\nits right place in the feed.\n\nOmitted fields keep their stored values, with one Ecto-level exception: as\non any edit, an update that does not carry `updated_at` stamps it to now.\nSend `updated_at` alongside `created_at` when repairing the dates of a row\nthat was never really edited.\n\nNever notifies, same as post/comment creation on this surface.\n","operationId":"BoldAdminWeb.Api.Admin.CommunityController.update_post","parameters":[{"description":"Post UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCommunityPostUpdate"}}},"description":"Post repair parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityPostResponse"}}},"description":"Post updated"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Post not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Validation errors"}},"security":[{"admin_api_token":[]}],"summary":"Repair an imported community post","tags":["Admin Community"]}},"/api/v1/admin/imports/{run_id}":{"get":{"callbacks":{},"description":"Tenant-scoped status, including non-public videos. done means video creation and processing handoff succeeded, not transcription/index readiness. total is null until playlist/channel expansion completes. Per-video errors are safe summaries, not raw provider logs. Videos are ordered by source URL and paginated independently of counts.","operationId":"BoldAdminWeb.Api.Admin.ImportController.show","parameters":[{"description":"Run UUID","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Video page number","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Videos per page","in":"query","name":"page_size","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminImportResponse"}}},"description":"Run"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Invalid pagination"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorResponse"}}},"description":"Run not found"}},"security":[{"admin_api_token":[]}],"summary":"Get import run and per-video results","tags":["Admin Imports"]}}},"security":[{"api_token":[]}],"servers":[{"description":"Production API","url":"https://app.boldvideo.io","variables":{}}],"tags":[]}