diff --git a/openapi.json b/openapi.json index ef9a355..692c175 100644 --- a/openapi.json +++ b/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "Sales CoPilot API", "version": "1.0.0", - "description": "REST API for Sales CoPilot — an AI-powered presentation platform with narrated slides, live Q&A, and deck management.\n\n**Authentication**: Most endpoints require a session cookie obtained from `POST /api/auth/login`. Presentation playback endpoints (`/api/qa`, `/api/narration/*`) also accept a `presentationId` for unauthenticated viewer access.", + "description": "REST API for Sales CoPilot — an AI-powered presentation platform with narrated slides, live Q&A, and deck management.\n\n**Authentication**: Most endpoints require a session cookie obtained from `POST /api/auth/login`. The `POST /api/decks/{deckId}/presentations` endpoint also accepts a Bearer token for programmatic access (e.g. CRM workflow integrations). Presentation playback endpoints (`/api/qa`, `/api/narration/*`) accept a `presentationId` for unauthenticated viewer access.", "contact": { "name": "Demand IQ", "url": "https://demand-iq.com" @@ -20,22 +20,70 @@ } ], "tags": [ - { "name": "Decks", "description": "Manage presentation decks" }, - { "name": "Slides", "description": "Manage slides within a deck" }, - { "name": "FAQs", "description": "Manage pre-built Q&A pairs" }, - { "name": "Branding", "description": "Colors, logo, and call-to-action configuration" }, - { "name": "Q&A", "description": "Live question answering and Q&A settings" }, - { "name": "Narration", "description": "Text-to-speech synthesis" }, - { "name": "Presentations", "description": "Create and manage personalized presentation instances" }, - { "name": "Generation", "description": "AI-powered content generation from slide images" }, - { "name": "Images", "description": "Image upload and management" }, - { "name": "Voices", "description": "Available TTS voices" }, - { "name": "Utility", "description": "Health check and diagnostics" }, - { "name": "Authentication", "description": "Session-based login, logout, and identity" }, - { "name": "Company", "description": "Organization contact info and knowledge base" }, - { "name": "Notifications", "description": "Event subscriptions and delivery log" }, - { "name": "Contracts", "description": "Contract template import and e-signature" }, - { "name": "Fonts", "description": "Available Google Fonts for branding" } + { + "name": "Decks", + "description": "Manage presentation decks" + }, + { + "name": "Slides", + "description": "Manage slides within a deck" + }, + { + "name": "FAQs", + "description": "Manage pre-built Q&A pairs" + }, + { + "name": "Branding", + "description": "Colors, logo, and call-to-action configuration" + }, + { + "name": "Q&A", + "description": "Live question answering and Q&A settings" + }, + { + "name": "Narration", + "description": "Text-to-speech synthesis" + }, + { + "name": "Presentations", + "description": "Create and manage personalized presentation instances" + }, + { + "name": "Generation", + "description": "AI-powered content generation from slide images" + }, + { + "name": "Images", + "description": "Image upload and management" + }, + { + "name": "Voices", + "description": "Available TTS voices" + }, + { + "name": "Utility", + "description": "Health check and diagnostics" + }, + { + "name": "Authentication", + "description": "Session-based login, logout, and identity" + }, + { + "name": "Company", + "description": "Organization contact info and knowledge base" + }, + { + "name": "Notifications", + "description": "Event subscriptions and delivery log" + }, + { + "name": "Contracts", + "description": "Contract template import and e-signature" + }, + { + "name": "Fonts", + "description": "Available Google Fonts for branding" + } ], "components": { "securitySchemes": { @@ -44,387 +92,1147 @@ "in": "cookie", "name": "session", "description": "Session cookie obtained from `POST /api/auth/login`." + }, + "bearerToken": { + "type": "http", + "scheme": "bearer", + "description": "Bearer token for programmatic API access (e.g. CRM integrations). Validated against the Demand IQ core API." } }, "schemas": { "WordTiming": { "type": "object", "properties": { - "word": { "type": "string" }, - "startTime": { "type": "number", "description": "Start time in milliseconds" }, - "endTime": { "type": "number", "description": "End time in milliseconds" } + "word": { + "type": "string" + }, + "startTime": { + "type": "number", + "description": "Start time in milliseconds" + }, + "endTime": { + "type": "number", + "description": "End time in milliseconds" + } }, - "required": ["word", "startTime", "endTime"] + "required": [ + "word", + "startTime", + "endTime" + ] }, "Deck": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "name": { "type": "string" }, - "description": { "type": "string", "nullable": true }, - "type": { "type": "string", "enum": ["structured", "image-based"] }, - "isActive": { "type": "boolean" }, + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "structured", + "image-based" + ] + }, + "isActive": { + "type": "boolean" + }, "metadata": { "type": "object", "nullable": true, "properties": { - "deckContext": { "type": "string", "description": "Background info used to guide AI narration generation" }, - "voiceProvider": { "type": "string", "enum": ["elevenlabs", "openai"] } + "deckContext": { + "type": "string", + "description": "Background info used to guide AI narration generation" + }, + "voiceProvider": { + "type": "string", + "enum": [ + "elevenlabs", + "openai" + ] + } } }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "name", "type", "isActive", "createdAt", "updatedAt"] + "required": [ + "id", + "name", + "type", + "isActive", + "createdAt", + "updatedAt" + ] }, "DeckSummary": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "name": { "type": "string" }, - "description": { "type": "string", "nullable": true }, - "type": { "type": "string", "enum": ["structured", "image-based"] }, - "isActive": { "type": "boolean" }, - "previewImageUrl": { "type": "string", "format": "uri", "nullable": true, "description": "Presigned URL for the first slide image" }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "structured", + "image-based" + ] + }, + "isActive": { + "type": "boolean" + }, + "previewImageUrl": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Presigned URL for the first slide image" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "name", "type", "isActive", "createdAt", "updatedAt"] + "required": [ + "id", + "name", + "type", + "isActive", + "createdAt", + "updatedAt" + ] }, "Slide": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "slideId": { "type": "string" }, - "title": { "type": "string" }, - "bullets": { "type": "array", "items": { "type": "string" } }, - "narration_script": { "type": "string" }, - "slide_context": { "type": "string", "nullable": true }, - "audio_url": { "type": "string", "nullable": true, "description": "S3 key for the slide audio file" }, - "hero_img": { "type": "string", "nullable": true, "description": "S3 key for the slide image" }, - "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" }, "nullable": true }, - "orderIndex": { "type": "integer" }, - "slideType": { "type": "string", "enum": ["standard", "product_pricing_v1"] } + "id": { + "type": "string", + "format": "uuid" + }, + "slideId": { + "type": "string" + }, + "title": { + "type": "string" + }, + "bullets": { + "type": "array", + "items": { + "type": "string" + } + }, + "narration_script": { + "type": "string" + }, + "slide_context": { + "type": "string", + "nullable": true + }, + "audio_url": { + "type": "string", + "nullable": true, + "description": "S3 key for the slide audio file" + }, + "hero_img": { + "type": "string", + "nullable": true, + "description": "S3 key for the slide image" + }, + "wordTimings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WordTiming" + }, + "nullable": true + }, + "orderIndex": { + "type": "integer" + }, + "slideType": { + "type": "string", + "enum": [ + "standard", + "product_pricing_v1" + ] + } }, - "required": ["id", "slideId", "title", "bullets", "narration_script", "orderIndex", "slideType"] + "required": [ + "id", + "slideId", + "title", + "bullets", + "narration_script", + "orderIndex", + "slideType" + ] }, "BrandingPalette": { "type": "object", "properties": { - "primary": { "type": "string", "example": "#1a73e8" }, - "primaryLight": { "type": "string", "example": "#4a9af5" }, - "accent": { "type": "string", "example": "#fbbc04" }, - "neutral": { "type": "string", "example": "#5f6368" }, - "text": { "type": "string", "example": "#202124" } + "primary": { + "type": "string", + "example": "#1a73e8" + }, + "primaryLight": { + "type": "string", + "example": "#4a9af5" + }, + "accent": { + "type": "string", + "example": "#fbbc04" + }, + "neutral": { + "type": "string", + "example": "#5f6368" + }, + "text": { + "type": "string", + "example": "#202124" + } }, - "required": ["primary", "primaryLight", "accent", "neutral", "text"] + "required": [ + "primary", + "primaryLight", + "accent", + "neutral", + "text" + ] }, "BrandingCta": { "type": "object", "properties": { - "headline": { "type": "string" }, - "body": { "type": "string" }, - "buttonText": { "type": "string" }, - "buttonUrl": { "type": "string", "format": "uri" } + "headline": { + "type": "string" + }, + "body": { + "type": "string" + }, + "buttonText": { + "type": "string" + }, + "buttonUrl": { + "type": "string", + "format": "uri" + } }, - "required": ["headline", "body", "buttonText"] + "required": [ + "headline", + "body", + "buttonText" + ] }, "Branding": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "deckId": { "type": "string", "format": "uuid" }, - "name": { "type": "string", "nullable": true }, - "logo": { "type": "string", "nullable": true, "description": "S3 key for logo image" }, - "avatar": { "type": "string", "nullable": true, "description": "S3 key for rep avatar image" }, - "chatName": { "type": "string", "nullable": true }, - "chatGreeting": { "type": "string", "nullable": true }, - "palette": { "$ref": "#/components/schemas/BrandingPalette" }, - "cta": { "$ref": "#/components/schemas/BrandingCta" }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "nullable": true + }, + "logo": { + "type": "string", + "nullable": true, + "description": "S3 key for logo image" + }, + "avatar": { + "type": "string", + "nullable": true, + "description": "S3 key for rep avatar image" + }, + "chatName": { + "type": "string", + "nullable": true + }, + "chatGreeting": { + "type": "string", + "nullable": true + }, + "palette": { + "$ref": "#/components/schemas/BrandingPalette" + }, + "cta": { + "$ref": "#/components/schemas/BrandingCta" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "deckId", "palette", "cta", "createdAt", "updatedAt"] + "required": [ + "id", + "deckId", + "palette", + "cta", + "createdAt", + "updatedAt" + ] }, "Faq": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "deckId": { "type": "string", "format": "uuid" }, - "userQuestion": { "type": "string" }, - "cannedAnswer": { "type": "string" }, - "relatedSlideId": { "type": "string", "nullable": true }, - "audioUrl": { "type": "string", "nullable": true, "description": "S3 key for the pre-generated answer audio" }, - "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" }, "nullable": true }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "userQuestion": { + "type": "string" + }, + "cannedAnswer": { + "type": "string" + }, + "relatedSlideId": { + "type": "string", + "nullable": true + }, + "audioUrl": { + "type": "string", + "nullable": true, + "description": "S3 key for the pre-generated answer audio" + }, + "wordTimings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WordTiming" + }, + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "deckId", "userQuestion", "cannedAnswer", "createdAt", "updatedAt"] + "required": [ + "id", + "deckId", + "userQuestion", + "cannedAnswer", + "createdAt", + "updatedAt" + ] }, "QaSettings": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "deckId": { "type": "string", "format": "uuid" }, - "resumePromptText": { "type": "string", "description": "Spoken prompt asking if the viewer wants to continue the presentation" }, - "resumeAudioUrl": { "type": "string", "nullable": true }, - "resumeVoiceId": { "type": "string", "nullable": true }, - "transitionPromptText": { "type": "string", "description": "Spoken bridge from Q&A back into the presentation" }, - "transitionAudioUrl": { "type": "string", "nullable": true }, - "transitionVoiceId": { "type": "string", "nullable": true } + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "resumePromptText": { + "type": "string", + "description": "Spoken prompt asking if the viewer wants to continue the presentation" + }, + "resumeAudioUrl": { + "type": "string", + "nullable": true + }, + "resumeVoiceId": { + "type": "string", + "nullable": true + }, + "transitionPromptText": { + "type": "string", + "description": "Spoken bridge from Q&A back into the presentation" + }, + "transitionAudioUrl": { + "type": "string", + "nullable": true + }, + "transitionVoiceId": { + "type": "string", + "nullable": true + } }, - "required": ["id", "deckId", "resumePromptText", "transitionPromptText"] + "required": [ + "id", + "deckId", + "resumePromptText", + "transitionPromptText" + ] }, "HomeownerData": { "type": "object", "properties": { - "first_name": { "type": "string" }, - "last_name": { "type": "string" }, - "address": { "type": "string" }, - "city": { "type": "string" }, - "state": { "type": "string" }, - "zip": { "type": "string" }, - "country": { "type": "string", "minLength": 2, "maxLength": 2, "description": "ISO 3166-1 alpha-2 country code", "example": "US" }, - "email": { "type": "string", "format": "email" }, - "phone": { "type": "string" } + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code", + "example": "US" + }, + "email": { + "type": "string", + "format": "email" + }, + "phone": { + "type": "string" + } }, - "required": ["first_name", "address", "city", "country"] + "required": [ + "first_name", + "address", + "city", + "country" + ] }, "Presentation": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "deckId": { "type": "string", "format": "uuid" }, - "companyId": { "type": "string", "format": "uuid" }, - "status": { "type": "string", "enum": ["not_started", "pending", "processing", "ready", "failed"] }, - "homeownerData": { "$ref": "#/components/schemas/HomeownerData" }, - "voiceSettings": { "type": "object", "nullable": true }, - "languageSettings": { "type": "object", "nullable": true }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" }, - "deletedAt": { "type": "string", "format": "date-time", "nullable": true } + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "companyId": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "not_started", + "pending", + "processing", + "ready", + "failed" + ] + }, + "homeownerData": { + "$ref": "#/components/schemas/HomeownerData" + }, + "voiceSettings": { + "type": "object", + "nullable": true + }, + "languageSettings": { + "type": "object", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "deletedAt": { + "type": "string", + "format": "date-time", + "nullable": true + } }, - "required": ["id", "deckId", "companyId", "status", "createdAt", "updatedAt"] + "required": [ + "id", + "deckId", + "companyId", + "status", + "createdAt", + "updatedAt" + ] }, "NarrationResponse": { "type": "object", "properties": { - "slideId": { "type": "string" }, - "script": { "type": "string" }, - "audioUrl": { "type": "string", "description": "Accessible URL for the synthesized audio" }, - "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } }, - "source": { "type": "string", "enum": ["elevenlabs", "openai", "fallback"] } + "slideId": { + "type": "string" + }, + "script": { + "type": "string" + }, + "audioUrl": { + "type": "string", + "description": "Accessible URL for the synthesized audio" + }, + "wordTimings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WordTiming" + } + }, + "source": { + "type": "string", + "enum": [ + "elevenlabs", + "openai", + "fallback" + ] + } }, - "required": ["slideId", "script", "audioUrl", "source"] + "required": [ + "slideId", + "script", + "audioUrl", + "source" + ] }, "QaResponse": { "type": "object", "properties": { - "answer": { "type": "string" }, - "audioUrl": { "type": "string", "description": "Accessible URL for the TTS audio of the answer" }, - "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } }, - "source": { "type": "string", "enum": ["faq", "cached", "openai", "fallback"] }, - "relatedSlideId": { "type": "string", "nullable": true }, + "answer": { + "type": "string" + }, + "audioUrl": { + "type": "string", + "description": "Accessible URL for the TTS audio of the answer" + }, + "wordTimings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WordTiming" + } + }, + "source": { + "type": "string", + "enum": [ + "faq", + "cached", + "openai", + "fallback" + ] + }, + "relatedSlideId": { + "type": "string", + "nullable": true + }, "structured": { "type": "object", "properties": { - "confidence": { "type": "string" }, - "source": { "type": "string" } + "confidence": { + "type": "string" + }, + "source": { + "type": "string" + } } } }, - "required": ["answer", "audioUrl", "source"] + "required": [ + "answer", + "audioUrl", + "source" + ] }, "GenerationStatus": { "type": "object", "properties": { - "jobId": { "type": "string", "format": "uuid" }, - "status": { "type": "string", "enum": ["pending", "processing", "completed", "failed"] }, - "currentSlide": { "type": "integer", "nullable": true }, - "totalSlides": { "type": "integer", "nullable": true }, - "error": { "type": "string", "nullable": true }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "jobId": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "processing", + "completed", + "failed" + ] + }, + "currentSlide": { + "type": "integer", + "nullable": true + }, + "totalSlides": { + "type": "integer", + "nullable": true + }, + "error": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["jobId", "status", "createdAt", "updatedAt"] + "required": [ + "jobId", + "status", + "createdAt", + "updatedAt" + ] }, "Image": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "organizationId": { "type": "string", "format": "uuid" }, - "filename": { "type": "string" }, - "s3Key": { "type": "string" }, - "mimeType": { "type": "string" }, - "size": { "type": "integer", "description": "File size in bytes" }, - "width": { "type": "integer", "nullable": true }, - "height": { "type": "integer", "nullable": true }, - "url": { "type": "string", "format": "uri", "description": "Presigned URL for the image" }, - "createdAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "filename": { + "type": "string" + }, + "s3Key": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer", + "description": "File size in bytes" + }, + "width": { + "type": "integer", + "nullable": true + }, + "height": { + "type": "integer", + "nullable": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Presigned URL for the image" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "organizationId", "filename", "s3Key", "mimeType", "size", "url", "createdAt"] + "required": [ + "id", + "organizationId", + "filename", + "s3Key", + "mimeType", + "size", + "url", + "createdAt" + ] }, "Voice": { "type": "object", "properties": { - "voice_id": { "type": "string" }, - "name": { "type": "string" }, - "category": { "type": "string" }, - "preview_url": { "type": "string", "format": "uri", "nullable": true }, - "labels": { "type": "object", "additionalProperties": { "type": "string" } } + "voice_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "category": { + "type": "string" + }, + "preview_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } }, - "required": ["voice_id", "name", "category"] + "required": [ + "voice_id", + "name", + "category" + ] }, "Error": { "type": "object", "properties": { - "error": { "type": "string" } + "error": { + "type": "string" + } }, - "required": ["error"] + "required": [ + "error" + ] }, "User": { "type": "object", "properties": { - "id": { "type": "string" }, - "email": { "type": "string", "format": "email" }, - "name": { "type": "string" } + "id": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + } }, - "required": ["id", "email", "name"] + "required": [ + "id", + "email", + "name" + ] }, "Organization": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "name": { "type": "string" } + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } }, - "required": ["id", "name"] + "required": [ + "id", + "name" + ] }, "CompanyContact": { "type": "object", "properties": { - "phone": { "type": "string", "nullable": true }, - "email": { "type": "string", "format": "email", "nullable": true }, - "address": { "type": "string", "nullable": true }, - "city": { "type": "string", "nullable": true }, - "state": { "type": "string", "nullable": true }, - "zip": { "type": "string", "nullable": true }, - "licenseNumber": { "type": "string", "nullable": true }, - "timezone": { "type": "string", "nullable": true } + "phone": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "nullable": true + }, + "zip": { + "type": "string", + "nullable": true + }, + "licenseNumber": { + "type": "string", + "nullable": true + }, + "timezone": { + "type": "string", + "nullable": true + } } }, "KbProfile": { "type": "object", "properties": { - "tenantId": { "type": "string", "format": "uuid" }, - "tagline": { "type": "string", "nullable": true }, - "about": { "type": "string", "nullable": true }, - "trustSignals": { "type": "string", "nullable": true }, - "serviceCoverage": { "type": "string", "nullable": true }, - "answerStyle": { "type": "string", "nullable": true } + "tenantId": { + "type": "string", + "format": "uuid" + }, + "tagline": { + "type": "string", + "nullable": true + }, + "about": { + "type": "string", + "nullable": true + }, + "trustSignals": { + "type": "string", + "nullable": true + }, + "serviceCoverage": { + "type": "string", + "nullable": true + }, + "answerStyle": { + "type": "string", + "nullable": true + } } }, "KbDocument": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "tenantId": { "type": "string", "format": "uuid" }, - "filename": { "type": "string" }, - "displayName": { "type": "string", "nullable": true }, - "contentType": { "type": "string" }, - "sizeBytes": { "type": "integer" }, - "status": { "type": "string", "enum": ["uploading", "pending", "processing", "ready", "failed"] }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "tenantId": { + "type": "string", + "format": "uuid" + }, + "filename": { + "type": "string" + }, + "displayName": { + "type": "string", + "nullable": true + }, + "contentType": { + "type": "string" + }, + "sizeBytes": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "uploading", + "pending", + "processing", + "ready", + "failed" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "tenantId", "filename", "contentType", "sizeBytes", "status", "createdAt"] + "required": [ + "id", + "tenantId", + "filename", + "contentType", + "sizeBytes", + "status", + "createdAt" + ] }, "ActionConfig": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "deckId": { "type": "string", "format": "uuid" }, - "actionType": { "type": "string" }, - "templateMarkdown": { "type": "string" }, - "disclosuresMarkdown": { "type": "string", "nullable": true }, - "requirementsJson": { "type": "object", "nullable": true }, - "repName": { "type": "string", "nullable": true }, - "status": { "type": "string", "enum": ["draft", "published"] }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "actionType": { + "type": "string" + }, + "templateMarkdown": { + "type": "string" + }, + "disclosuresMarkdown": { + "type": "string", + "nullable": true + }, + "requirementsJson": { + "type": "object", + "nullable": true + }, + "repName": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "deckId", "actionType", "templateMarkdown", "status", "createdAt", "updatedAt"] + "required": [ + "id", + "deckId", + "actionType", + "templateMarkdown", + "status", + "createdAt", + "updatedAt" + ] }, "IntroConfig": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "deckId": { "type": "string", "format": "uuid" }, - "backgroundImage": { "type": "string", "description": "S3 key or URL for intro background image" }, - "headline": { "type": "string" }, - "subtitle": { "type": "string", "nullable": true }, - "ctaButtonLabel": { "type": "string", "nullable": true } + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "backgroundImage": { + "type": "string", + "description": "S3 key or URL for intro background image" + }, + "headline": { + "type": "string" + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "ctaButtonLabel": { + "type": "string", + "nullable": true + } }, - "required": ["id", "deckId", "backgroundImage", "headline"] + "required": [ + "id", + "deckId", + "backgroundImage", + "headline" + ] }, "NotificationSubscription": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "organizationId": { "type": "string", "format": "uuid" }, - "name": { "type": "string" }, - "eventType": { "type": "string" }, - "channel": { "type": "string", "enum": ["webhook", "email", "console"] }, - "channelConfig": { "type": "object", "description": "Channel-specific configuration (e.g. URL for webhook, recipients for email)" }, - "deckId": { "type": "string", "format": "uuid", "nullable": true }, - "isActive": { "type": "boolean" }, - "createdAt": { "type": "string", "format": "date-time" }, - "updatedAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "eventType": { + "type": "string" + }, + "channel": { + "type": "string", + "enum": [ + "webhook", + "email", + "console" + ] + }, + "channelConfig": { + "type": "object", + "description": "Channel-specific configuration (e.g. URL for webhook, recipients for email)" + }, + "deckId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "isActive": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "organizationId", "name", "eventType", "channel", "channelConfig", "isActive", "createdAt", "updatedAt"] + "required": [ + "id", + "organizationId", + "name", + "eventType", + "channel", + "channelConfig", + "isActive", + "createdAt", + "updatedAt" + ] }, "EventLogEntry": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "eventType": { "type": "string" }, - "aggregateType": { "type": "string" }, - "aggregateId": { "type": "string" }, - "payload": { "type": "object" }, - "occurredAt": { "type": "string", "format": "date-time" } + "id": { + "type": "string", + "format": "uuid" + }, + "eventType": { + "type": "string" + }, + "aggregateType": { + "type": "string" + }, + "aggregateId": { + "type": "string" + }, + "payload": { + "type": "object" + }, + "occurredAt": { + "type": "string", + "format": "date-time" + } }, - "required": ["id", "eventType", "aggregateType", "aggregateId", "occurredAt"] + "required": [ + "id", + "eventType", + "aggregateType", + "aggregateId", + "occurredAt" + ] }, "ProductSlideData": { "type": "object", "description": "Good/better/best product pricing tiers for a product pricing slide.", "properties": { - "good": { "type": "object", "nullable": true }, - "better": { "type": "object", "nullable": true }, - "best": { "type": "object", "nullable": true }, - "subtitle": { "type": "string", "nullable": true }, - "brandLogo": { "type": "string", "nullable": true }, - "disclaimer": { "type": "string", "nullable": true }, - "defaultSelectedTier": { "type": "string", "enum": ["good", "better", "best"], "nullable": true } + "good": { + "type": "object", + "nullable": true + }, + "better": { + "type": "object", + "nullable": true + }, + "best": { + "type": "object", + "nullable": true + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "brandLogo": { + "type": "string", + "nullable": true + }, + "disclaimer": { + "type": "string", + "nullable": true + }, + "defaultSelectedTier": { + "type": "string", + "enum": [ + "good", + "better", + "best" + ], + "nullable": true + } } }, "Font": { "type": "object", "properties": { - "family": { "type": "string", "example": "Inter" }, - "variants": { "type": "array", "items": { "type": "string" } }, - "category": { "type": "string", "example": "sans-serif" } + "family": { + "type": "string", + "example": "Inter" + }, + "variants": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "type": "string", + "example": "sans-serif" + } }, - "required": ["family", "variants", "category"] + "required": [ + "family", + "variants", + "category" + ] } } }, "paths": { "/api/decks": { "get": { - "tags": ["Decks"], + "tags": [ + "Decks" + ], "summary": "List decks", "description": "Returns all decks for the authenticated organization, ordered by most recently created.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "List of decks", @@ -435,7 +1243,9 @@ "properties": { "decks": { "type": "array", - "items": { "$ref": "#/components/schemas/DeckSummary" } + "items": { + "$ref": "#/components/schemas/DeckSummary" + } } } } @@ -444,31 +1254,71 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "post": { - "tags": ["Decks"], + "tags": [ + "Decks" + ], "summary": "Create a deck", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": ["name"], + "required": [ + "name" + ], "properties": { - "name": { "type": "string" }, - "description": { "type": "string" }, - "type": { "type": "string", "enum": ["structured", "image-based"], "default": "structured" }, - "deckContext": { "type": "string", "description": "Background information to guide AI narration generation (e.g. audience, purpose)" }, - "voiceProvider": { "type": "string", "enum": ["elevenlabs", "openai"] } + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "structured", + "image-based" + ], + "default": "structured" + }, + "deckContext": { + "type": "string", + "description": "Background information to guide AI narration generation (e.g. audience, purpose)" + }, + "voiceProvider": { + "type": "string", + "enum": [ + "elevenlabs", + "openai" + ] + } } } } @@ -482,7 +1332,9 @@ "schema": { "type": "object", "properties": { - "deck": { "$ref": "#/components/schemas/Deck" } + "deck": { + "$ref": "#/components/schemas/Deck" + } } } } @@ -490,31 +1342,58 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}": { "get": { - "tags": ["Decks"], + "tags": [ + "Decks" + ], "summary": "Get a deck", "description": "Returns the full deck including slides, FAQs, branding, and Q&A settings.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -526,41 +1405,88 @@ "schema": { "type": "object", "properties": { - "deck": { "$ref": "#/components/schemas/Deck" }, - "slides": { "type": "array", "items": { "$ref": "#/components/schemas/Slide" } }, - "faqs": { "type": "array", "items": { "$ref": "#/components/schemas/Faq" } }, - "branding": { "nullable": true, "$ref": "#/components/schemas/Branding" }, - "qaSettings": { "nullable": true, "$ref": "#/components/schemas/QaSettings" }, - "presentationsCount": { "type": "integer" } - } - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } - }, + "deck": { + "$ref": "#/components/schemas/Deck" + }, + "slides": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Slide" + } + }, + "faqs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Faq" + } + }, + "branding": { + "nullable": true, + "$ref": "#/components/schemas/Branding" + }, + "qaSettings": { + "nullable": true, + "$ref": "#/components/schemas/QaSettings" + }, + "presentationsCount": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "put": { - "tags": ["Decks"], + "tags": [ + "Decks" + ], "summary": "Update a deck", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -571,11 +1497,27 @@ "schema": { "type": "object", "properties": { - "name": { "type": "string" }, - "description": { "type": "string", "nullable": true }, - "type": { "type": "string", "enum": ["structured", "image-based"] }, - "isActive": { "type": "boolean" }, - "deckContext": { "type": "string", "nullable": true } + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "structured", + "image-based" + ] + }, + "isActive": { + "type": "boolean" + }, + "deckContext": { + "type": "string", + "nullable": true + } } } } @@ -589,7 +1531,9 @@ "schema": { "type": "object", "properties": { - "deck": { "$ref": "#/components/schemas/Deck" } + "deck": { + "$ref": "#/components/schemas/Deck" + } } } } @@ -597,33 +1541,66 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "delete": { - "tags": ["Decks"], + "tags": [ + "Decks" + ], "summary": "Delete a deck", "description": "Permanently deletes a deck and all associated slides, FAQs, and branding.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -635,7 +1612,9 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } @@ -643,30 +1622,57 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/slides": { "get": { - "tags": ["Slides"], + "tags": [ + "Slides" + ], "summary": "List slides", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -678,7 +1684,12 @@ "schema": { "type": "object", "properties": { - "slides": { "type": "array", "items": { "$ref": "#/components/schemas/Slide" } } + "slides": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Slide" + } + } } } } @@ -686,28 +1697,55 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "post": { - "tags": ["Slides"], + "tags": [ + "Slides" + ], "summary": "Create a slide", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -717,17 +1755,52 @@ "application/json": { "schema": { "type": "object", - "required": ["slideId", "title", "narrationScript"], + "required": [ + "slideId", + "title", + "narrationScript" + ], "properties": { - "slideId": { "type": "string", "description": "Human-readable identifier (e.g. \"slide-1\")" }, - "title": { "type": "string" }, - "bullets": { "type": "array", "items": { "type": "string" } }, - "narrationScript": { "type": "string" }, - "slideContext": { "type": "string", "nullable": true }, - "audioUrl": { "type": "string", "description": "S3 key for pre-recorded audio" }, - "heroImg": { "type": "string", "nullable": true, "description": "S3 key for slide image" }, - "orderIndex": { "type": "integer" }, - "slideType": { "type": "string", "enum": ["standard", "product_pricing_v1"], "default": "standard" } + "slideId": { + "type": "string", + "description": "Human-readable identifier (e.g. \"slide-1\")" + }, + "title": { + "type": "string" + }, + "bullets": { + "type": "array", + "items": { + "type": "string" + } + }, + "narrationScript": { + "type": "string" + }, + "slideContext": { + "type": "string", + "nullable": true + }, + "audioUrl": { + "type": "string", + "description": "S3 key for pre-recorded audio" + }, + "heroImg": { + "type": "string", + "nullable": true, + "description": "S3 key for slide image" + }, + "orderIndex": { + "type": "integer" + }, + "slideType": { + "type": "string", + "enum": [ + "standard", + "product_pricing_v1" + ], + "default": "standard" + } } } } @@ -741,7 +1814,9 @@ "schema": { "type": "object", "properties": { - "slide": { "$ref": "#/components/schemas/Slide" } + "slide": { + "$ref": "#/components/schemas/Slide" + } } } } @@ -749,34 +1824,67 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/slides/reorder": { "put": { - "tags": ["Slides"], + "tags": [ + "Slides" + ], "summary": "Reorder slides", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -786,11 +1894,16 @@ "application/json": { "schema": { "type": "object", - "required": ["slideIds"], + "required": [ + "slideIds" + ], "properties": { "slideIds": { "type": "array", - "items": { "type": "string", "format": "uuid" }, + "items": { + "type": "string", + "format": "uuid" + }, "description": "Full ordered array of slide UUIDs. Position in array becomes the new orderIndex." } } @@ -806,7 +1919,9 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } @@ -814,34 +1929,67 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/slides/{slideId}": { "put": { - "tags": ["Slides"], + "tags": [ + "Slides" + ], "summary": "Update a slide", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "slideId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the slide" } ], @@ -852,13 +2000,32 @@ "schema": { "type": "object", "properties": { - "title": { "type": "string" }, - "bullets": { "type": "array", "items": { "type": "string" } }, - "narrationScript": { "type": "string" }, - "slideContext": { "type": "string", "nullable": true }, - "audioUrl": { "type": "string" }, - "heroImg": { "type": "string", "nullable": true }, - "orderIndex": { "type": "integer" } + "title": { + "type": "string" + }, + "bullets": { + "type": "array", + "items": { + "type": "string" + } + }, + "narrationScript": { + "type": "string" + }, + "slideContext": { + "type": "string", + "nullable": true + }, + "audioUrl": { + "type": "string" + }, + "heroImg": { + "type": "string", + "nullable": true + }, + "orderIndex": { + "type": "integer" + } } } } @@ -872,7 +2039,9 @@ "schema": { "type": "object", "properties": { - "slide": { "$ref": "#/components/schemas/Slide" } + "slide": { + "$ref": "#/components/schemas/Slide" + } } } } @@ -880,32 +2049,65 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "delete": { - "tags": ["Slides"], + "tags": [ + "Slides" + ], "summary": "Delete a slide", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "slideId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the slide" } ], @@ -917,7 +2119,9 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } @@ -925,30 +2129,57 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/faqs": { "get": { - "tags": ["FAQs"], + "tags": [ + "FAQs" + ], "summary": "List FAQs", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -960,7 +2191,12 @@ "schema": { "type": "object", "properties": { - "faqs": { "type": "array", "items": { "$ref": "#/components/schemas/Faq" } } + "faqs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Faq" + } + } } } } @@ -968,29 +2204,56 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "post": { - "tags": ["FAQs"], + "tags": [ + "FAQs" + ], "summary": "Create a FAQ", "description": "Creates a FAQ and automatically generates TTS audio for the answer.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1000,11 +2263,22 @@ "application/json": { "schema": { "type": "object", - "required": ["userQuestion", "cannedAnswer"], + "required": [ + "userQuestion", + "cannedAnswer" + ], "properties": { - "userQuestion": { "type": "string" }, - "cannedAnswer": { "type": "string" }, - "relatedSlideId": { "type": "string", "format": "uuid", "nullable": true } + "userQuestion": { + "type": "string" + }, + "cannedAnswer": { + "type": "string" + }, + "relatedSlideId": { + "type": "string", + "format": "uuid", + "nullable": true + } } } } @@ -1018,8 +2292,13 @@ "schema": { "type": "object", "properties": { - "faq": { "$ref": "#/components/schemas/Faq" }, - "warning": { "type": "string", "description": "Present if audio generation failed (FAQ is still saved)" } + "faq": { + "$ref": "#/components/schemas/Faq" + }, + "warning": { + "type": "string", + "description": "Present if audio generation failed (FAQ is still saved)" + } } } } @@ -1027,34 +2306,67 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/faqs/{faqId}": { "put": { - "tags": ["FAQs"], + "tags": [ + "FAQs" + ], "summary": "Update a FAQ", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "faqId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the FAQ" } ], @@ -1065,9 +2377,17 @@ "schema": { "type": "object", "properties": { - "userQuestion": { "type": "string" }, - "cannedAnswer": { "type": "string" }, - "relatedSlideId": { "type": "string", "format": "uuid", "nullable": true } + "userQuestion": { + "type": "string" + }, + "cannedAnswer": { + "type": "string" + }, + "relatedSlideId": { + "type": "string", + "format": "uuid", + "nullable": true + } } } } @@ -1081,7 +2401,9 @@ "schema": { "type": "object", "properties": { - "faq": { "$ref": "#/components/schemas/Faq" } + "faq": { + "$ref": "#/components/schemas/Faq" + } } } } @@ -1089,32 +2411,65 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "delete": { - "tags": ["FAQs"], + "tags": [ + "FAQs" + ], "summary": "Delete a FAQ", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "faqId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the FAQ" } ], @@ -1126,7 +2481,9 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } @@ -1134,30 +2491,57 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/branding": { "get": { - "tags": ["Branding"], + "tags": [ + "Branding" + ], "summary": "Get branding", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1169,7 +2553,10 @@ "schema": { "type": "object", "properties": { - "branding": { "nullable": true, "$ref": "#/components/schemas/Branding" } + "branding": { + "nullable": true, + "$ref": "#/components/schemas/Branding" + } } } } @@ -1177,29 +2564,56 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "put": { - "tags": ["Branding"], + "tags": [ + "Branding" + ], "summary": "Upsert branding", "description": "Creates or replaces the branding configuration for a deck.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1209,21 +2623,51 @@ "application/json": { "schema": { "type": "object", - "required": ["palette", "cta"], + "required": [ + "palette", + "cta" + ], "properties": { - "name": { "type": "string" }, - "logo": { "type": "string", "nullable": true, "description": "S3 key for logo image" }, - "avatar": { "type": "string", "nullable": true, "description": "S3 key for rep avatar image" }, - "chatName": { "type": "string", "nullable": true }, - "chatGreeting": { "type": "string", "nullable": true }, - "palette": { "$ref": "#/components/schemas/BrandingPalette" }, - "cta": { "$ref": "#/components/schemas/BrandingCta" }, - "fonts": { "type": "object" }, + "name": { + "type": "string" + }, + "logo": { + "type": "string", + "nullable": true, + "description": "S3 key for logo image" + }, + "avatar": { + "type": "string", + "nullable": true, + "description": "S3 key for rep avatar image" + }, + "chatName": { + "type": "string", + "nullable": true + }, + "chatGreeting": { + "type": "string", + "nullable": true + }, + "palette": { + "$ref": "#/components/schemas/BrandingPalette" + }, + "cta": { + "$ref": "#/components/schemas/BrandingCta" + }, + "fonts": { + "type": "object" + }, "contact": { "type": "object", "properties": { - "phone": { "type": "string" }, - "email": { "type": "string", "format": "email" } + "phone": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } } } } @@ -1239,7 +2683,9 @@ "schema": { "type": "object", "properties": { - "branding": { "$ref": "#/components/schemas/Branding" } + "branding": { + "$ref": "#/components/schemas/Branding" + } } } } @@ -1247,35 +2693,68 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/qa-settings": { "get": { - "tags": ["Q&A"], + "tags": [ + "Q&A" + ], "summary": "Get Q&A settings", "description": "Returns the customizable resume and transition prompts for the deck's Q&A experience.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1287,7 +2766,10 @@ "schema": { "type": "object", "properties": { - "qaSettings": { "nullable": true, "$ref": "#/components/schemas/QaSettings" } + "qaSettings": { + "nullable": true, + "$ref": "#/components/schemas/QaSettings" + } } } } @@ -1295,28 +2777,55 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "put": { - "tags": ["Q&A"], + "tags": [ + "Q&A" + ], "summary": "Update Q&A settings", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1327,10 +2836,18 @@ "schema": { "type": "object", "properties": { - "resumePromptText": { "type": "string" }, - "resumeVoiceId": { "type": "string" }, - "transitionPromptText": { "type": "string" }, - "transitionVoiceId": { "type": "string" } + "resumePromptText": { + "type": "string" + }, + "resumeVoiceId": { + "type": "string" + }, + "transitionPromptText": { + "type": "string" + }, + "transitionVoiceId": { + "type": "string" + } } } } @@ -1344,7 +2861,9 @@ "schema": { "type": "object", "properties": { - "qaSettings": { "$ref": "#/components/schemas/QaSettings" } + "qaSettings": { + "$ref": "#/components/schemas/QaSettings" + } } } } @@ -1352,35 +2871,68 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/generate": { "post": { - "tags": ["Generation"], + "tags": [ + "Generation" + ], "summary": "Start AI generation from images", "description": "Accepts uploaded slide images, runs them through GPT-4o Vision to extract content, generates narration scripts, and synthesizes audio. Returns a job ID — poll `/api/decks/{deckId}/generate/status` for progress.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1390,11 +2942,16 @@ "multipart/form-data": { "schema": { "type": "object", - "required": ["images"], + "required": [ + "images" + ], "properties": { "images": { "type": "array", - "items": { "type": "string", "format": "binary" }, + "items": { + "type": "string", + "format": "binary" + }, "description": "Slide images (PNG, JPG, or WebP; max 10 MB each)" } } @@ -1410,7 +2967,10 @@ "schema": { "type": "object", "properties": { - "jobId": { "type": "string", "format": "uuid" } + "jobId": { + "type": "string", + "format": "uuid" + } } } } @@ -1418,34 +2978,67 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } - }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/generate/status": { "get": { - "tags": ["Generation"], + "tags": [ + "Generation" + ], "summary": "Poll generation status", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1454,37 +3047,66 @@ "description": "Current job status", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/GenerationStatus" } + "schema": { + "$ref": "#/components/schemas/GenerationStatus" + } } } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/decks/{deckId}/presentations": { "get": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "List presentations", "description": "Returns all non-deleted presentations created from this deck.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1496,7 +3118,12 @@ "schema": { "type": "object", "properties": { - "presentations": { "type": "array", "items": { "$ref": "#/components/schemas/Presentation" } } + "presentations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Presentation" + } + } } } } @@ -1504,29 +3131,59 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "post": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Create a presentation", - "description": "Creates a personalized presentation for a homeowner. The address is validated via Google Geocoding. Returns a shareable URL immediately; audio generation may continue in the background.", - "security": [{ "sessionCookie": [] }], + "description": "Creates a personalized presentation for a homeowner. Accepts session cookie or Bearer token authentication (for programmatic integrations such as CRM workflows). The address is validated via Google Geocoding. Returns a shareable URL immediately; audio generation may continue in the background.", + "security": [ + { + "sessionCookie": [] + }, + { + "bearerToken": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the deck" } ], @@ -1536,19 +3193,32 @@ "application/json": { "schema": { "type": "object", - "required": ["homeowner"], + "required": [ + "homeowner" + ], "properties": { - "homeowner": { "$ref": "#/components/schemas/HomeownerData" }, - "voice_settings": { "type": "object" }, - "language_settings": { "type": "object" }, + "homeowner": { + "$ref": "#/components/schemas/HomeownerData" + }, + "voice_settings": { + "type": "object" + }, + "language_settings": { + "type": "object" + }, "price_inputs": { "type": "object", - "additionalProperties": { "type": "number" }, + "additionalProperties": { + "type": "number" + }, "description": "Values for deck-level price variables (used to evaluate formulas)" }, "measurement_strategy": { "type": "string", - "enum": ["manual", "auto"], + "enum": [ + "manual", + "auto" + ], "default": "manual" } } @@ -1564,9 +3234,25 @@ "schema": { "type": "object", "properties": { - "deck_presentation_id": { "type": "string", "format": "uuid" }, - "url": { "type": "string", "format": "uri", "description": "Shareable presentation URL" }, - "status": { "type": "string", "enum": ["not_started", "pending", "processing", "ready", "failed"] } + "deck_presentation_id": { + "type": "string", + "format": "uuid" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Shareable presentation URL" + }, + "status": { + "type": "string", + "enum": [ + "not_started", + "pending", + "processing", + "ready", + "failed" + ] + } } } } @@ -1574,30 +3260,62 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "422": { "description": "Unprocessable entity", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/presentations/{presentationId}": { "patch": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Update a presentation", "description": "Partial update for homeowner data, voice settings, language, or price inputs.", "parameters": [ @@ -1605,7 +3323,10 @@ "in": "path", "name": "presentationId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the presentation" } ], @@ -1616,10 +3337,23 @@ "schema": { "type": "object", "properties": { - "homeownerData": { "type": "object" }, - "voiceSettings": { "type": "object", "nullable": true }, - "languageSettings": { "type": "object", "nullable": true }, - "priceInputs": { "type": "object", "additionalProperties": { "type": "number" } } + "homeownerData": { + "type": "object" + }, + "voiceSettings": { + "type": "object", + "nullable": true + }, + "languageSettings": { + "type": "object", + "nullable": true + }, + "priceInputs": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } } } } @@ -1633,8 +3367,13 @@ "schema": { "type": "object", "properties": { - "message": { "type": "string" }, - "presentationId": { "type": "string", "format": "uuid" } + "message": { + "type": "string" + }, + "presentationId": { + "type": "string", + "format": "uuid" + } } } } @@ -1642,20 +3381,40 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } }, "delete": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Delete a presentation", "description": "Soft-deletes a presentation (sets `deletedAt`). The shareable URL stops working immediately.", "parameters": [ @@ -1663,7 +3422,10 @@ "in": "path", "name": "presentationId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the presentation" } ], @@ -1675,8 +3437,13 @@ "schema": { "type": "object", "properties": { - "message": { "type": "string" }, - "presentationId": { "type": "string", "format": "uuid" } + "message": { + "type": "string" + }, + "presentationId": { + "type": "string", + "format": "uuid" + } } } } @@ -1684,22 +3451,42 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/presentations/{presentationId}/status": { "get": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Get presentation status", "description": "Returns the current processing status and slide readiness.", "parameters": [ @@ -1707,7 +3494,10 @@ "in": "path", "name": "presentationId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the presentation" } ], @@ -1719,9 +3509,22 @@ "schema": { "type": "object", "properties": { - "status": { "type": "string", "enum": ["not_started", "pending", "processing", "ready", "failed"] }, - "slidesReady": { "type": "integer" }, - "totalSlides": { "type": "integer" } + "status": { + "type": "string", + "enum": [ + "not_started", + "pending", + "processing", + "ready", + "failed" + ] + }, + "slidesReady": { + "type": "integer" + }, + "totalSlides": { + "type": "integer" + } } } } @@ -1729,18 +3532,32 @@ }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/qa": { "post": { - "tags": ["Q&A"], + "tags": [ + "Q&A" + ], "summary": "Answer a question", "description": "Answers a viewer question. Checks FAQs first (exact then semantic match at >= 0.6 confidence), falls back to GPT-4o with slide context. Returns answer text plus synthesized audio.\n\n**Authentication**: Accepts either a session cookie (admin) or a `presentationId` (public viewer).", "requestBody": { @@ -1749,25 +3566,58 @@ "application/json": { "schema": { "type": "object", - "required": ["question", "slideId"], + "required": [ + "question", + "slideId" + ], "properties": { - "question": { "type": "string", "minLength": 3 }, - "slideId": { "type": "string", "description": "Current slide ID for context" }, - "deckId": { "type": "string", "format": "uuid" }, - "presentationId": { "type": "string", "format": "uuid", "description": "Required for unauthenticated viewer access" }, + "question": { + "type": "string", + "minLength": 3 + }, + "slideId": { + "type": "string", + "description": "Current slide ID for context" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "presentationId": { + "type": "string", + "format": "uuid", + "description": "Required for unauthenticated viewer access" + }, "history": { "type": "array", "items": { "type": "object", - "required": ["role", "content"], + "required": [ + "role", + "content" + ], "properties": { - "role": { "type": "string", "enum": ["user", "assistant"] }, - "content": { "type": "string" } + "role": { + "type": "string", + "enum": [ + "user", + "assistant" + ] + }, + "content": { + "type": "string" + } } } }, - "voiceId": { "type": "string" }, - "language": { "type": "string", "example": "en", "description": "BCP-47 language tag" } + "voiceId": { + "type": "string" + }, + "language": { + "type": "string", + "example": "en", + "description": "BCP-47 language tag" + } } } } @@ -1778,36 +3628,70 @@ "description": "Answer with audio", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/QaResponse" } + "schema": { + "$ref": "#/components/schemas/QaResponse" + } } } }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "403": { "description": "Forbidden", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/narration/single": { "post": { - "tags": ["Narration"], + "tags": [ + "Narration" + ], "summary": "Synthesize narration for one slide", "description": "Generates TTS audio for a slide script. Results are cached per session.", "requestBody": { @@ -1816,13 +3700,31 @@ "application/json": { "schema": { "type": "object", - "required": ["sessionId", "slideId", "script"], + "required": [ + "sessionId", + "slideId", + "script" + ], "properties": { - "sessionId": { "type": "string", "description": "Unique session identifier for caching" }, - "slideId": { "type": "string" }, - "script": { "type": "string", "minLength": 1 }, - "voiceId": { "type": "string" }, - "presentationId": { "type": "string", "format": "uuid", "description": "Required for unauthenticated viewer access" } + "sessionId": { + "type": "string", + "description": "Unique session identifier for caching" + }, + "slideId": { + "type": "string" + }, + "script": { + "type": "string", + "minLength": 1 + }, + "voiceId": { + "type": "string" + }, + "presentationId": { + "type": "string", + "format": "uuid", + "description": "Required for unauthenticated viewer access" + } } } } @@ -1833,32 +3735,60 @@ "description": "Audio URL and word timings", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/NarrationResponse" } + "schema": { + "$ref": "#/components/schemas/NarrationResponse" + } } } }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "403": { "description": "Forbidden", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/narration/batch": { "post": { - "tags": ["Narration"], + "tags": [ + "Narration" + ], "summary": "Synthesize narration for multiple slides", "description": "Batch TTS generation. Per-slide failures return a fallback audio URL rather than failing the whole batch.", "requestBody": { @@ -1867,19 +3797,34 @@ "application/json": { "schema": { "type": "object", - "required": ["sessionId", "slides"], + "required": [ + "sessionId", + "slides" + ], "properties": { - "sessionId": { "type": "string" }, + "sessionId": { + "type": "string" + }, "slides": { "type": "array", "minItems": 1, "items": { "type": "object", - "required": ["slideId", "script"], + "required": [ + "slideId", + "script" + ], "properties": { - "slideId": { "type": "string" }, - "script": { "type": "string", "minLength": 1 }, - "voiceId": { "type": "string" } + "slideId": { + "type": "string" + }, + "script": { + "type": "string", + "minLength": 1 + }, + "voiceId": { + "type": "string" + } } } } @@ -1896,8 +3841,18 @@ "schema": { "type": "object", "properties": { - "cachedIds": { "type": "array", "items": { "type": "string" } }, - "responses": { "type": "array", "items": { "$ref": "#/components/schemas/NarrationResponse" } } + "cachedIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "responses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NarrationResponse" + } + } } } } @@ -1905,22 +3860,42 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/narration/resume": { "post": { - "tags": ["Narration"], + "tags": [ + "Narration" + ], "summary": "Synthesize resume narration", "description": "Generates the spoken bridge from Q&A back into the presentation, using the deck's configured transition prompt.", "requestBody": { @@ -1930,10 +3905,21 @@ "schema": { "type": "object", "properties": { - "deckId": { "type": "string", "format": "uuid" }, - "presentationId": { "type": "string", "format": "uuid" }, - "voiceId": { "type": "string" }, - "language": { "type": "string", "example": "en" } + "deckId": { + "type": "string", + "format": "uuid" + }, + "presentationId": { + "type": "string", + "format": "uuid" + }, + "voiceId": { + "type": "string" + }, + "language": { + "type": "string", + "example": "en" + } } } } @@ -1944,35 +3930,67 @@ "description": "Resume narration audio", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/NarrationResponse" } + "schema": { + "$ref": "#/components/schemas/NarrationResponse" + } } } }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "403": { "description": "Forbidden", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/images": { "get": { - "tags": ["Images"], + "tags": [ + "Images" + ], "summary": "List images", "description": "Returns all uploaded images for the authenticated organization.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "List of images", @@ -1981,7 +3999,12 @@ "schema": { "type": "object", "properties": { - "images": { "type": "array", "items": { "$ref": "#/components/schemas/Image" } } + "images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Image" + } + } } } } @@ -1989,30 +4012,54 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/images/upload": { "post": { - "tags": ["Images"], + "tags": [ + "Images" + ], "summary": "Upload an image", "description": "Uploads and optimizes an image (max 1920px width, JPEG quality 85). Returns the S3 key and a presigned URL.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", - "required": ["file"], + "required": [ + "file" + ], "properties": { - "file": { "type": "string", "format": "binary", "description": "Image file (PNG, JPG, or WebP; max 10 MB)" } + "file": { + "type": "string", + "format": "binary", + "description": "Image file (PNG, JPG, or WebP; max 10 MB)" + } } } } @@ -2026,7 +4073,9 @@ "schema": { "type": "object", "properties": { - "image": { "$ref": "#/components/schemas/Image" } + "image": { + "$ref": "#/components/schemas/Image" + } } } } @@ -2034,30 +4083,57 @@ }, "400": { "description": "Bad request", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/images/{imageId}": { "delete": { - "tags": ["Images"], + "tags": [ + "Images" + ], "summary": "Delete an image", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "imageId", "required": true, - "schema": { "type": "string", "format": "uuid" }, + "schema": { + "type": "string", + "format": "uuid" + }, "description": "UUID of the image" } ], @@ -2069,7 +4145,9 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } @@ -2077,25 +4155,49 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "404": { "description": "Not found", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/voices/list": { "get": { - "tags": ["Voices"], + "tags": [ + "Voices" + ], "summary": "List available voices", "description": "Returns a curated list of voices suitable for sales presentations.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "Voice list with preview URLs", @@ -2104,7 +4206,12 @@ "schema": { "type": "object", "properties": { - "voices": { "type": "array", "items": { "$ref": "#/components/schemas/Voice" } } + "voices": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Voice" + } + } } } } @@ -2112,18 +4219,32 @@ }, "401": { "description": "Unauthorized", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, "500": { "description": "Internal server error", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, "/api/health": { "get": { - "tags": ["Utility"], + "tags": [ + "Utility" + ], "summary": "Health check", "description": "Returns `{ ok: true }` if the service is running.", "responses": { @@ -2134,7 +4255,9 @@ "schema": { "type": "object", "properties": { - "ok": { "type": "boolean" } + "ok": { + "type": "boolean" + } } } } @@ -2145,7 +4268,9 @@ }, "/api/auth/login": { "post": { - "tags": ["Authentication"], + "tags": [ + "Authentication" + ], "summary": "Log in", "description": "Authenticates with email and password. Returns user and organization info and sets a session cookie. Rate-limited to prevent brute-force attacks.", "requestBody": { @@ -2154,10 +4279,20 @@ "application/json": { "schema": { "type": "object", - "required": ["email", "password"], + "required": [ + "email", + "password" + ], "properties": { - "email": { "type": "string", "format": "email", "example": "rep@acmesolar.com" }, - "password": { "type": "string", "format": "password" } + "email": { + "type": "string", + "format": "email", + "example": "rep@acmesolar.com" + }, + "password": { + "type": "string", + "format": "password" + } } } } @@ -2171,26 +4306,72 @@ "schema": { "type": "object", "properties": { - "user": { "$ref": "#/components/schemas/User" }, - "organization": { "$ref": "#/components/schemas/Organization" } + "user": { + "$ref": "#/components/schemas/User" + }, + "organization": { + "$ref": "#/components/schemas/Organization" + } } } } } }, - "400": { "description": "Missing email or password", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Missing email or password", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/auth/logout": { "post": { - "tags": ["Authentication"], + "tags": [ + "Authentication" + ], "summary": "Log out", "description": "Revokes the current session and clears the session cookie.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "Logged out", @@ -2199,7 +4380,9 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } @@ -2210,10 +4393,16 @@ }, "/api/auth/me": { "get": { - "tags": ["Authentication"], + "tags": [ + "Authentication" + ], "summary": "Get current user", "description": "Returns the authenticated user's identity and organization.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "Current user", @@ -2222,24 +4411,52 @@ "schema": { "type": "object", "properties": { - "user": { "$ref": "#/components/schemas/User" }, - "organization": { "$ref": "#/components/schemas/Organization" } + "user": { + "$ref": "#/components/schemas/User" + }, + "organization": { + "$ref": "#/components/schemas/Organization" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/company/contact": { "get": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Get company contact info", "description": "Returns the organization's contact details used in contracts and branding.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "Company contact", @@ -2248,26 +4465,56 @@ "schema": { "type": "object", "properties": { - "contact": { "$ref": "#/components/schemas/CompanyContact" }, - "organizationName": { "type": "string" } + "contact": { + "$ref": "#/components/schemas/CompanyContact" + }, + "organizationName": { + "type": "string" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "put": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Update company contact info", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/CompanyContact" } + "schema": { + "$ref": "#/components/schemas/CompanyContact" + } } } }, @@ -2279,46 +4526,98 @@ "schema": { "type": "object", "properties": { - "contact": { "$ref": "#/components/schemas/CompanyContact" } + "contact": { + "$ref": "#/components/schemas/CompanyContact" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - } - }, - "/api/company/kb/profile": { - "get": { - "tags": ["Company"], - "summary": "Get knowledge base profile", - "description": "Returns the company's knowledge base profile that guides how the AI answers customer questions.", - "security": [{ "sessionCookie": [] }], - "responses": { - "200": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/company/kb/profile": { + "get": { + "tags": [ + "Company" + ], + "summary": "Get knowledge base profile", + "description": "Returns the company's knowledge base profile that guides how the AI answers customer questions.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { "description": "KB profile", "content": { "application/json": { "schema": { "type": "object", "properties": { - "profile": { "$ref": "#/components/schemas/KbProfile" } + "profile": { + "$ref": "#/components/schemas/KbProfile" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "put": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Update knowledge base profile", "description": "Updates the company tagline, about text, trust signals, service coverage, and answer style used by the AI.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { @@ -2326,11 +4625,21 @@ "schema": { "type": "object", "properties": { - "tagline": { "type": "string" }, - "about": { "type": "string" }, - "trustSignals": { "type": "string" }, - "serviceCoverage": { "type": "string" }, - "answerStyle": { "type": "string" } + "tagline": { + "type": "string" + }, + "about": { + "type": "string" + }, + "trustSignals": { + "type": "string" + }, + "serviceCoverage": { + "type": "string" + }, + "answerStyle": { + "type": "string" + } } } } @@ -2344,23 +4653,49 @@ "schema": { "type": "object", "properties": { - "profile": { "$ref": "#/components/schemas/KbProfile" } + "profile": { + "$ref": "#/components/schemas/KbProfile" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/company/kb/documents": { "get": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "List knowledge base documents", "description": "Returns all uploaded knowledge base documents for the organization, with usage metadata.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "KB documents", @@ -2369,13 +4704,24 @@ "schema": { "type": "object", "properties": { - "documents": { "type": "array", "items": { "$ref": "#/components/schemas/KbDocument" } }, + "documents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KbDocument" + } + }, "meta": { "type": "object", "properties": { - "count": { "type": "integer" }, - "limit": { "type": "integer" }, - "remaining": { "type": "integer" } + "count": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "remaining": { + "type": "integer" + } } } } @@ -2383,29 +4729,68 @@ } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/company/kb/documents/upload-init": { "post": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Initiate document upload", "description": "Validates the file and returns a presigned S3 URL for direct upload. Call `/api/company/kb/documents/complete` after the upload finishes.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": ["filename", "contentType", "sizeBytes"], + "required": [ + "filename", + "contentType", + "sizeBytes" + ], "properties": { - "filename": { "type": "string", "example": "product-guide.pdf" }, - "contentType": { "type": "string", "example": "application/pdf" }, - "sizeBytes": { "type": "integer", "example": 1048576 }, - "displayName": { "type": "string" } + "filename": { + "type": "string", + "example": "product-guide.pdf" + }, + "contentType": { + "type": "string", + "example": "application/pdf" + }, + "sizeBytes": { + "type": "integer", + "example": 1048576 + }, + "displayName": { + "type": "string" + } } } } @@ -2419,36 +4804,86 @@ "schema": { "type": "object", "properties": { - "documentId": { "type": "string", "format": "uuid" }, - "uploadUrl": { "type": "string", "format": "uri", "description": "Presigned S3 URL for PUT upload" }, - "storageKey": { "type": "string" }, - "expiresIn": { "type": "integer", "description": "URL expiry in seconds" } + "documentId": { + "type": "string", + "format": "uuid" + }, + "uploadUrl": { + "type": "string", + "format": "uri", + "description": "Presigned S3 URL for PUT upload" + }, + "storageKey": { + "type": "string" + }, + "expiresIn": { + "type": "integer", + "description": "URL expiry in seconds" + } } } } } }, - "400": { "description": "Bad request (invalid file type, size, or document limit reached)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Bad request (invalid file type, size, or document limit reached)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/company/kb/documents/complete": { "post": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Complete document upload", "description": "Marks a document as uploaded after the client finishes uploading to S3. Triggers background processing (text extraction, chunking, and embedding).", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": ["documentId"], + "required": [ + "documentId" + ], "properties": { - "documentId": { "type": "string", "format": "uuid" } + "documentId": { + "type": "string", + "format": "uuid" + } } } } @@ -2462,31 +4897,82 @@ "schema": { "type": "object", "properties": { - "status": { "type": "string", "example": "pending" }, - "documentId": { "type": "string", "format": "uuid" } + "status": { + "type": "string", + "example": "pending" + }, + "documentId": { + "type": "string", + "format": "uuid" + } } } } } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Document not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/company/kb/documents/{documentId}": { "get": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Get a knowledge base document", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "documentId", "required": true, - "schema": { "type": "string", "format": "uuid" } + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { @@ -2497,28 +4983,66 @@ "schema": { "type": "object", "properties": { - "document": { "$ref": "#/components/schemas/KbDocument" } + "document": { + "$ref": "#/components/schemas/KbDocument" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "delete": { - "tags": ["Company"], + "tags": [ + "Company" + ], "summary": "Delete a knowledge base document", "description": "Removes the document from S3 and the database. Associated chunks are deleted automatically.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "documentId", "required": true, - "schema": { "type": "string", "format": "uuid" } + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { @@ -2529,33 +5053,74 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" } + "success": { + "type": "boolean" + } } } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/contracts/import": { "post": { - "tags": ["Contracts"], + "tags": [ + "Contracts" + ], "summary": "Import a contract document", "description": "Uploads a contract document (PDF or Word), extracts text, and uses AI to analyze it for template conversion. Returns detected variables that can be used in contract templates.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", - "required": ["file"], + "required": [ + "file" + ], "properties": { - "file": { "type": "string", "format": "binary", "description": "Contract file (PDF or DOCX, max 10 MB)" } + "file": { + "type": "string", + "format": "binary", + "description": "Contract file (PDF or DOCX, max 10 MB)" + } } } } @@ -2569,30 +5134,71 @@ "schema": { "type": "object", "properties": { - "detectedVariables": { "type": "array", "items": { "type": "string" } } + "detectedVariables": { + "type": "array", + "items": { + "type": "string" + } + } } } } } }, - "400": { "description": "Bad request (unsupported file type or too large)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Bad request (unsupported file type or too large)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/decks/{deckId}/action-configs": { "get": { - "tags": ["Contracts"], + "tags": [ + "Contracts" + ], "summary": "List action configs", "description": "Returns all action configurations (contract templates) for a deck.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" } + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { @@ -2602,27 +5208,65 @@ "application/json": { "schema": { "type": "array", - "items": { "$ref": "#/components/schemas/ActionConfig" } + "items": { + "$ref": "#/components/schemas/ActionConfig" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "post": { - "tags": ["Contracts"], + "tags": [ + "Contracts" + ], "summary": "Create an action config", "description": "Creates a new contract template or action configuration for a deck.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ { "in": "path", "name": "deckId", "required": true, - "schema": { "type": "string", "format": "uuid" } + "schema": { + "type": "string", + "format": "uuid" + } } ], "requestBody": { @@ -2631,14 +5275,35 @@ "application/json": { "schema": { "type": "object", - "required": ["actionType", "templateMarkdown"], + "required": [ + "actionType", + "templateMarkdown" + ], "properties": { - "actionType": { "type": "string", "example": "loi" }, - "templateMarkdown": { "type": "string" }, - "disclosuresMarkdown": { "type": "string" }, - "requirementsJson": { "type": "object" }, - "repName": { "type": "string" }, - "status": { "type": "string", "enum": ["draft", "published"], "default": "draft" } + "actionType": { + "type": "string", + "example": "loi" + }, + "templateMarkdown": { + "type": "string" + }, + "disclosuresMarkdown": { + "type": "string" + }, + "requirementsJson": { + "type": "object" + }, + "repName": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "default": "draft" + } } } } @@ -2649,211 +5314,158 @@ "description": "Action config created", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/ActionConfig" } + "schema": { + "$ref": "#/components/schemas/ActionConfig" + } } } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - } - }, - "/api/decks/{deckId}/action-configs/{configId}": { - "get": { - "tags": ["Contracts"], - "summary": "Get an action config", - "security": [{ "sessionCookie": [] }], - "parameters": [ - { "in": "path", "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" } }, - { "in": "path", "name": "configId", "required": true, "schema": { "type": "string", "format": "uuid" } } - ], - "responses": { - "200": { - "description": "Action config", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ActionConfig" } } } + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - }, - "put": { - "tags": ["Contracts"], - "summary": "Update an action config", - "security": [{ "sessionCookie": [] }], - "parameters": [ - { "in": "path", "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" } }, - { "in": "path", "name": "configId", "required": true, "schema": { "type": "string", "format": "uuid" } } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "templateMarkdown": { "type": "string" }, - "disclosuresMarkdown": { "type": "string" }, - "requirementsJson": { "type": "object" }, - "repName": { "type": "string" }, - "status": { "type": "string", "enum": ["draft", "published"] } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } - } - }, - "responses": { - "200": { - "description": "Updated action config", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ActionConfig" } } } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - }, - "delete": { - "tags": ["Contracts"], - "summary": "Delete an action config", - "security": [{ "sessionCookie": [] }], - "parameters": [ - { "in": "path", "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" } }, - { "in": "path", "name": "configId", "required": true, "schema": { "type": "string", "format": "uuid" } } - ], - "responses": { - "200": { - "description": "Action config deleted", - "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, - "/api/decks/{deckId}/intro": { + "/api/decks/{deckId}/action-configs/{configId}": { "get": { - "tags": ["Decks"], - "summary": "Get intro page config", - "description": "Returns the intro page configuration for a deck, including the background image, headline, and CTA button label.", + "tags": [ + "Contracts" + ], + "summary": "Get an action config", + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "path", "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "configId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "responses": { "200": { - "description": "Intro configuration, or null if not set", + "description": "Action config", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "intro": { "nullable": true, "$ref": "#/components/schemas/IntroConfig" } - } + "$ref": "#/components/schemas/ActionConfig" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - }, - "put": { - "tags": ["Decks"], - "summary": "Upsert intro page config", - "description": "Creates or updates the intro page for a deck. Enabling an intro page switches the deck to lazy generation mode.", - "security": [{ "sessionCookie": [] }], - "parameters": [ - { "in": "path", "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" } } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["backgroundImage", "headline"], - "properties": { - "backgroundImage": { "type": "string", "description": "S3 key for the intro background image" }, - "headline": { "type": "string", "example": "Your Custom Solar Proposal" }, - "subtitle": { "type": "string" }, - "ctaButtonLabel": { "type": "string", "example": "View My Proposal" } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } - } - }, - "responses": { - "200": { - "description": "Saved intro config", + }, + "404": { + "description": "Not found", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "intro": { "$ref": "#/components/schemas/IntroConfig" } - } + "$ref": "#/components/schemas/Error" } } } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - }, - "delete": { - "tags": ["Decks"], - "summary": "Delete intro page config", - "security": [{ "sessionCookie": [] }], - "parameters": [ - { "in": "path", "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" } } - ], - "responses": { - "200": { - "description": "Intro deleted", - "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } - }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } - } - } - }, - "/api/slides/{slideId}/product-data": { - "get": { - "tags": ["Slides"], - "summary": "Get product pricing data", - "description": "Returns the good/better/best product pricing tiers for a product pricing slide.", - "security": [{ "sessionCookie": [] }], - "parameters": [ - { "in": "path", "name": "slideId", "required": true, "schema": { "type": "string", "format": "uuid" } } - ], - "responses": { - "200": { - "description": "Product data", + "500": { + "description": "Internal server error", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "productData": { "$ref": "#/components/schemas/ProductSlideData" } - } + "$ref": "#/components/schemas/Error" } } } - }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + } } }, "put": { - "tags": ["Slides"], - "summary": "Upsert product pricing data", - "description": "Sets product pricing tiers for a slide. The slide type is automatically set to `product_pricing_v1`.", - "security": [{ "sessionCookie": [] }], + "tags": [ + "Contracts" + ], + "summary": "Update an action config", + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "path", "name": "slideId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "configId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -2861,9 +5473,26 @@ "application/json": { "schema": { "type": "object", - "required": ["productData"], "properties": { - "productData": { "$ref": "#/components/schemas/ProductSlideData" } + "templateMarkdown": { + "type": "string" + }, + "disclosuresMarkdown": { + "type": "string" + }, + "requirementsJson": { + "type": "object" + }, + "repName": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + } } } } @@ -2871,40 +5500,586 @@ }, "responses": { "200": { - "description": "Product data saved", - "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } + "description": "Updated action config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActionConfig" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Contracts" + ], + "summary": "Delete an action config", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "configId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Action config deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/intro": { + "get": { + "tags": [ + "Decks" + ], + "summary": "Get intro page config", + "description": "Returns the intro page configuration for a deck, including the background image, headline, and CTA button label.", + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Intro configuration, or null if not set", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "intro": { + "nullable": true, + "$ref": "#/components/schemas/IntroConfig" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Decks" + ], + "summary": "Upsert intro page config", + "description": "Creates or updates the intro page for a deck. Enabling an intro page switches the deck to lazy generation mode.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "backgroundImage", + "headline" + ], + "properties": { + "backgroundImage": { + "type": "string", + "description": "S3 key for the intro background image" + }, + "headline": { + "type": "string", + "example": "Your Custom Solar Proposal" + }, + "subtitle": { + "type": "string" + }, + "ctaButtonLabel": { + "type": "string", + "example": "View My Proposal" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Saved intro config", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "intro": { + "$ref": "#/components/schemas/IntroConfig" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Decks" + ], + "summary": "Delete intro page config", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Intro deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/slides/{slideId}/product-data": { + "get": { + "tags": [ + "Slides" + ], + "summary": "Get product pricing data", + "description": "Returns the good/better/best product pricing tiers for a product pricing slide.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "slideId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Product data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "productData": { + "$ref": "#/components/schemas/ProductSlideData" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Slides" + ], + "summary": "Upsert product pricing data", + "description": "Sets product pricing tiers for a slide. The slide type is automatically set to `product_pricing_v1`.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "slideId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "productData" + ], + "properties": { + "productData": { + "$ref": "#/components/schemas/ProductSlideData" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Product data saved", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "delete": { - "tags": ["Slides"], + "tags": [ + "Slides" + ], "summary": "Remove product pricing data", "description": "Removes product data and resets the slide type to `standard`.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "path", "name": "slideId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "slideId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "responses": { "200": { "description": "Product data removed", - "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/notifications/subscriptions": { "get": { - "tags": ["Notifications"], + "tags": [ + "Notifications" + ], "summary": "List notification subscriptions", "description": "Returns all notification subscriptions for the organization.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "responses": { "200": { "description": "Subscriptions", @@ -2912,34 +6087,88 @@ "application/json": { "schema": { "type": "array", - "items": { "$ref": "#/components/schemas/NotificationSubscription" } + "items": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "post": { - "tags": ["Notifications"], + "tags": [ + "Notifications" + ], "summary": "Create a notification subscription", "description": "Subscribes to an event type via a delivery channel (webhook, email, or console).", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": ["name", "eventType", "channel", "channelConfig"], + "required": [ + "name", + "eventType", + "channel", + "channelConfig" + ], "properties": { - "name": { "type": "string", "example": "Slack — new presentation" }, - "eventType": { "type": "string", "example": "presentation.created" }, - "channel": { "type": "string", "enum": ["webhook", "email", "console"] }, - "channelConfig": { "type": "object", "description": "Channel-specific config. Webhook: `{ url }`. Email: `{ recipients: [\"a@b.com\"] }`." }, - "deckId": { "type": "string", "format": "uuid", "description": "Scope to a specific deck (optional)" }, - "isActive": { "type": "boolean", "default": true } + "name": { + "type": "string", + "example": "Slack — new presentation" + }, + "eventType": { + "type": "string", + "example": "presentation.created" + }, + "channel": { + "type": "string", + "enum": [ + "webhook", + "email", + "console" + ] + }, + "channelConfig": { + "type": "object", + "description": "Channel-specific config. Webhook: `{ url }`. Email: `{ recipients: [\"a@b.com\"] }`." + }, + "deckId": { + "type": "string", + "format": "uuid", + "description": "Scope to a specific deck (optional)" + }, + "isActive": { + "type": "boolean", + "default": true + } } } } @@ -2948,38 +6177,132 @@ "responses": { "201": { "description": "Subscription created", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotificationSubscription" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/notifications/subscriptions/{subscriptionId}": { "get": { - "tags": ["Notifications"], + "tags": [ + "Notifications" + ], "summary": "Get a notification subscription", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "path", "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "subscriptionId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "responses": { "200": { "description": "Subscription", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotificationSubscription" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "put": { - "tags": ["Notifications"], + "tags": [ + "Notifications" + ], "summary": "Update a notification subscription", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "path", "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "subscriptionId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -2988,12 +6311,31 @@ "schema": { "type": "object", "properties": { - "name": { "type": "string" }, - "eventType": { "type": "string" }, - "channel": { "type": "string", "enum": ["webhook", "email", "console"] }, - "channelConfig": { "type": "object" }, - "deckId": { "type": "string", "format": "uuid", "nullable": true }, - "isActive": { "type": "boolean" } + "name": { + "type": "string" + }, + "eventType": { + "type": "string" + }, + "channel": { + "type": "string", + "enum": [ + "webhook", + "email", + "console" + ] + }, + "channelConfig": { + "type": "object" + }, + "deckId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "isActive": { + "type": "boolean" + } } } } @@ -3002,46 +6344,195 @@ "responses": { "200": { "description": "Updated subscription", - "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotificationSubscription" } } } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "delete": { - "tags": ["Notifications"], + "tags": [ + "Notifications" + ], "summary": "Delete a notification subscription", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "path", "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "subscriptionId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "responses": { "200": { "description": "Subscription deleted", - "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/notifications/events": { "get": { - "tags": ["Notifications"], + "tags": [ + "Notifications" + ], "summary": "List event log", "description": "Returns a paginated, filterable log of all events for the organization.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "parameters": [ - { "in": "query", "name": "eventType", "schema": { "type": "string" }, "description": "Filter by event type" }, - { "in": "query", "name": "aggregateType", "schema": { "type": "string" } }, - { "in": "query", "name": "aggregateId", "schema": { "type": "string" } }, - { "in": "query", "name": "since", "schema": { "type": "string", "format": "date-time" } }, - { "in": "query", "name": "until", "schema": { "type": "string", "format": "date-time" } }, - { "in": "query", "name": "limit", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } }, - { "in": "query", "name": "offset", "schema": { "type": "integer", "default": 0 } } + { + "in": "query", + "name": "eventType", + "schema": { + "type": "string" + }, + "description": "Filter by event type" + }, + { + "in": "query", + "name": "aggregateType", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "aggregateId", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "default": 0 + } + } ], "responses": { "200": { @@ -3051,13 +6542,24 @@ "schema": { "type": "object", "properties": { - "events": { "type": "array", "items": { "$ref": "#/components/schemas/EventLogEntry" } }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventLogEntry" + } + }, "pagination": { "type": "object", "properties": { - "total": { "type": "integer" }, - "limit": { "type": "integer" }, - "offset": { "type": "integer" } + "total": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + } } } } @@ -3065,18 +6567,46 @@ } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/presentations/{presentationId}/start": { "post": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Start a presentation", "description": "Triggers lazy generation when a viewer clicks the CTA on the intro page. Safe to call multiple times — duplicate generation requests are ignored.", "parameters": [ - { "in": "path", "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "responses": { "200": { @@ -3086,28 +6616,79 @@ "schema": { "type": "object", "properties": { - "id": { "type": "string", "format": "uuid" }, - "status": { "type": "string", "enum": ["ready", "generating"] }, - "phase": { "type": "string", "nullable": true }, - "message": { "type": "string" } + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "ready", + "generating" + ] + }, + "phase": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string" + } } } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "410": { "description": "Presentation deleted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "410": { + "description": "Presentation deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/presentations/{presentationId}/product-selection": { "get": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Get product selections", "description": "Returns all product tier selections the viewer has made during the presentation.", "parameters": [ - { "in": "path", "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "responses": { "200": { @@ -3117,22 +6698,55 @@ "schema": { "type": "object", "properties": { - "selectedProducts": { "type": "object", "additionalProperties": { "type": "object" } } + "selectedProducts": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } } } } } - }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "put": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Save a product selection", "description": "Saves a product tier selection for a specific slide during the presentation.", "parameters": [ - { "in": "path", "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -3140,15 +6754,42 @@ "application/json": { "schema": { "type": "object", - "required": ["slideId", "tier", "productName", "price"], + "required": [ + "slideId", + "tier", + "productName", + "price" + ], "properties": { - "slideId": { "type": "string" }, - "tier": { "type": "string", "enum": ["good", "better", "best"] }, - "productName": { "type": "string" }, - "price": { "type": "string" }, - "priceSuffix": { "type": "string" }, - "productImage": { "type": "string" }, - "highlights": { "type": "array", "items": { "type": "string" } } + "slideId": { + "type": "string" + }, + "tier": { + "type": "string", + "enum": [ + "good", + "better", + "best" + ] + }, + "productName": { + "type": "string" + }, + "price": { + "type": "string" + }, + "priceSuffix": { + "type": "string" + }, + "productImage": { + "type": "string" + }, + "highlights": { + "type": "array", + "items": { + "type": "string" + } + } } } } @@ -3162,27 +6803,76 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" }, - "selection": { "type": "object" } + "success": { + "type": "boolean" + }, + "selection": { + "type": "object" + } } } } } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/presentations/{presentationId}/sign-contract": { "get": { - "tags": ["Contracts"], + "tags": [ + "Contracts" + ], "summary": "Get contract for signing", "description": "Returns the contract template, company branding, and product data needed to render the signing view.", "parameters": [ - { "in": "path", "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" } }, - { "in": "query", "name": "configId", "schema": { "type": "string", "format": "uuid" }, "description": "Action config to use (uses published config if omitted)" } + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "configId", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Action config to use (uses published config if omitted)" + } ], "responses": { "200": { @@ -3192,29 +6882,77 @@ "schema": { "type": "object", "properties": { - "template": { "type": "string" }, - "requirements": { "type": "object", "nullable": true }, - "repName": { "type": "string", "nullable": true }, - "companyName": { "type": "string" }, - "companyLogo": { "type": "string", "nullable": true }, - "companyDetails": { "type": "object" }, - "branding": { "type": "object", "nullable": true }, - "products": { "type": "object" } + "template": { + "type": "string" + }, + "requirements": { + "type": "object", + "nullable": true + }, + "repName": { + "type": "string", + "nullable": true + }, + "companyName": { + "type": "string" + }, + "companyLogo": { + "type": "string", + "nullable": true + }, + "companyDetails": { + "type": "object" + }, + "branding": { + "type": "object", + "nullable": true + }, + "products": { + "type": "object" + } } } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } }, "post": { - "tags": ["Contracts"], + "tags": [ + "Contracts" + ], "summary": "Sign a contract", "description": "Submits a contract signature. Validates the typed signature against the customer name, resolves the template with variables, and creates an audit-trailed signed document.", "parameters": [ - { "in": "path", "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -3222,21 +6960,42 @@ "application/json": { "schema": { "type": "object", - "required": ["variables", "signature"], + "required": [ + "variables", + "signature" + ], "properties": { - "configId": { "type": "string", "format": "uuid" }, + "configId": { + "type": "string", + "format": "uuid" + }, "variables": { "type": "object", - "required": ["full_name"], + "required": [ + "full_name" + ], "properties": { - "full_name": { "type": "string" }, - "first_name": { "type": "string" }, - "last_name": { "type": "string" } + "full_name": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + } }, - "additionalProperties": { "type": "string" } + "additionalProperties": { + "type": "string" + } + }, + "signature": { + "type": "string", + "description": "Typed signature (must match the customer name)" }, - "signature": { "type": "string", "description": "Typed signature (must match the customer name)" }, - "agreedToTerms": { "type": "boolean" } + "agreedToTerms": { + "type": "boolean" + } } } } @@ -3250,28 +7009,75 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean" }, - "documentId": { "type": "string", "format": "uuid" }, - "signedAt": { "type": "string", "format": "date-time" }, - "message": { "type": "string" } + "success": { + "type": "boolean" + }, + "documentId": { + "type": "string", + "format": "uuid" + }, + "signedAt": { + "type": "string", + "format": "date-time" + }, + "message": { + "type": "string" + } } } } } }, - "400": { "description": "Bad request (signature mismatch or validation error)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Bad request (signature mismatch or validation error)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/presentations/{presentationId}/events": { "post": { - "tags": ["Presentations"], + "tags": [ + "Presentations" + ], "summary": "Record a presentation event", "description": "Ingests interaction events from the presentation viewer (slide views, CTA clicks, Q&A logs). Authenticated via an HMAC token in the request body.", "parameters": [ - { "in": "path", "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" } } + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], "requestBody": { "required": true, @@ -3279,15 +7085,41 @@ "application/json": { "schema": { "type": "object", - "required": ["_token", "type"], + "required": [ + "_token", + "type" + ], "properties": { - "_token": { "type": "string", "description": "HMAC authentication token" }, - "type": { "type": "string", "enum": ["presentation_opened", "slide_viewed", "cta_clicked", "action_detection", "action_execution"], "description": "Event type" }, - "slideId": { "type": "string" }, - "slideIndex": { "type": "integer" }, - "durationMs": { "type": "integer" }, - "ctaType": { "type": "string" }, - "ctaLabel": { "type": "string" } + "_token": { + "type": "string", + "description": "HMAC authentication token" + }, + "type": { + "type": "string", + "enum": [ + "presentation_opened", + "slide_viewed", + "cta_clicked", + "action_detection", + "action_execution" + ], + "description": "Event type" + }, + "slideId": { + "type": "string" + }, + "slideIndex": { + "type": "integer" + }, + "durationMs": { + "type": "integer" + }, + "ctaType": { + "type": "string" + }, + "ctaLabel": { + "type": "string" + } } } } @@ -3296,34 +7128,109 @@ "responses": { "200": { "description": "Event recorded", - "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Invalid token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Invalid token", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/address/validate": { "post": { - "tags": ["Utility"], + "tags": [ + "Utility" + ], "summary": "Validate an address", "description": "Validates a physical address using geocoding. Returns formatted address, coordinates, and normalized components.", - "security": [{ "sessionCookie": [] }], + "security": [ + { + "sessionCookie": [] + } + ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": ["address", "city", "country"], + "required": [ + "address", + "city", + "country" + ], "properties": { - "address": { "type": "string", "example": "123 Main St" }, - "city": { "type": "string", "example": "Phoenix" }, - "state": { "type": "string", "example": "AZ" }, - "zip": { "type": "string", "example": "85001" }, - "country": { "type": "string", "minLength": 2, "maxLength": 2, "example": "US", "description": "ISO 3166-1 alpha-2 country code" } + "address": { + "type": "string", + "example": "123 Main St" + }, + "city": { + "type": "string", + "example": "Phoenix" + }, + "state": { + "type": "string", + "example": "AZ" + }, + "zip": { + "type": "string", + "example": "85001" + }, + "country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "example": "US", + "description": "ISO 3166-1 alpha-2 country code" + } } } } @@ -3337,32 +7244,82 @@ "schema": { "type": "object", "properties": { - "valid": { "type": "boolean" }, - "formattedAddress": { "type": "string" }, + "valid": { + "type": "boolean" + }, + "formattedAddress": { + "type": "string" + }, "coordinates": { "type": "object", "properties": { - "lat": { "type": "number" }, - "lng": { "type": "number" } + "lat": { + "type": "number" + }, + "lng": { + "type": "number" + } } }, - "normalized": { "type": "object" }, - "error": { "type": "string" } + "normalized": { + "type": "object" + }, + "error": { + "type": "string" + } } } } } }, - "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "503": { "description": "Validation service not configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "503": { + "description": "Validation service not configured", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } }, "/api/fonts": { "get": { - "tags": ["Fonts"], + "tags": [ + "Fonts" + ], "summary": "List available fonts", "description": "Returns a list of available Google Fonts for use in deck branding. Results are cached for 24 hours.", "responses": { @@ -3373,13 +7330,27 @@ "schema": { "type": "object", "properties": { - "fonts": { "type": "array", "items": { "$ref": "#/components/schemas/Font" } } + "fonts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Font" + } + } } } } } }, - "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } } } } diff --git a/sales-copilot/introduction.mdx b/sales-copilot/introduction.mdx index d45596d..420526f 100644 --- a/sales-copilot/introduction.mdx +++ b/sales-copilot/introduction.mdx @@ -20,7 +20,12 @@ Sales CoPilot by Demand IQ lets you build AI-narrated sales presentations with i ## Authentication -Most API endpoints require a session cookie obtained from `POST /api/auth/login`. Presentation playback endpoints (`/api/qa`, `/api/narration/*`) also accept a `presentationId` for unauthenticated viewer access. +Most API endpoints require a session cookie obtained from `POST /api/auth/login`. The `POST /api/decks/{deckId}/presentations` endpoint also accepts a Bearer token for programmatic access, making it easy to create presentations from CRM workflows or other automated systems. Presentation playback endpoints (`/api/qa`, `/api/narration/*`) accept a `presentationId` for unauthenticated viewer access. + +| Method | Use case | Header / cookie | +|---|---|---| +| Session cookie | Admin UI, interactive use | `session` cookie from `POST /api/auth/login` | +| Bearer token | CRM integrations, automated workflows | `Authorization: Bearer ` | ## Get started diff --git a/sales-copilot/openapi.json b/sales-copilot/openapi.json index f5f0d96..692c175 100644 --- a/sales-copilot/openapi.json +++ b/sales-copilot/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "Sales CoPilot API", "version": "1.0.0", - "description": "REST API for Sales CoPilot — an AI-powered presentation platform with narrated slides, live Q&A, and deck management.\n\n**Authentication**: Most endpoints require a session cookie obtained from `POST /api/auth/login`. Presentation playback endpoints (`/api/qa`, `/api/narration/*`) also accept a `presentationId` for unauthenticated viewer access.", + "description": "REST API for Sales CoPilot — an AI-powered presentation platform with narrated slides, live Q&A, and deck management.\n\n**Authentication**: Most endpoints require a session cookie obtained from `POST /api/auth/login`. The `POST /api/decks/{deckId}/presentations` endpoint also accepts a Bearer token for programmatic access (e.g. CRM workflow integrations). Presentation playback endpoints (`/api/qa`, `/api/narration/*`) accept a `presentationId` for unauthenticated viewer access.", "contact": { "name": "Demand IQ", "url": "https://demand-iq.com" @@ -63,6 +63,26 @@ { "name": "Utility", "description": "Health check and diagnostics" + }, + { + "name": "Authentication", + "description": "Session-based login, logout, and identity" + }, + { + "name": "Company", + "description": "Organization contact info and knowledge base" + }, + { + "name": "Notifications", + "description": "Event subscriptions and delivery log" + }, + { + "name": "Contracts", + "description": "Contract template import and e-signature" + }, + { + "name": "Fonts", + "description": "Available Google Fonts for branding" } ], "components": { @@ -72,6 +92,11 @@ "in": "cookie", "name": "session", "description": "Session cookie obtained from `POST /api/auth/login`." + }, + "bearerToken": { + "type": "http", + "scheme": "bearer", + "description": "Bearer token for programmatic API access (e.g. CRM integrations). Validated against the Demand IQ core API." } }, "schemas": { @@ -794,219 +819,3603 @@ "required": [ "error" ] - } - } - }, - "paths": { - "/api/decks": { - "get": { - "tags": [ - "Decks" - ], - "summary": "List decks", - "description": "Returns all decks for the authenticated organization, ordered by most recently created.", - "security": [ - { - "sessionCookie": [] - } - ], - "responses": { - "200": { - "description": "List of decks", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "decks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DeckSummary" - } - } - } - } - } - } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "email": { + "type": "string", + "format": "email" }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "name": { + "type": "string" } - } + }, + "required": [ + "id", + "email", + "name" + ] }, - "post": { - "tags": [ - "Decks" - ], - "summary": "Create a deck", - "security": [ - { - "sessionCookie": [] - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "structured", - "image-based" - ], - "default": "structured" - }, - "deckContext": { - "type": "string", - "description": "Background information to guide AI narration generation (e.g. audience, purpose)" - }, - "voiceProvider": { - "type": "string", - "enum": [ - "elevenlabs", - "openai" - ] - } - } - } - } + "Organization": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" } }, - "responses": { - "201": { - "description": "Deck created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "deck": { - "$ref": "#/components/schemas/Deck" - } - } - } - } - } + "required": [ + "id", + "name" + ] + }, + "CompanyContact": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "nullable": true }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "email": { + "type": "string", + "format": "email", + "nullable": true }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "address": { + "type": "string", + "nullable": true }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "city": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "nullable": true + }, + "zip": { + "type": "string", + "nullable": true + }, + "licenseNumber": { + "type": "string", + "nullable": true + }, + "timezone": { + "type": "string", + "nullable": true } } - } - }, - "/api/decks/{deckId}": { - "get": { - "tags": [ - "Decks" - ], - "summary": "Get a deck", - "description": "Returns the full deck including slides, FAQs, branding, and Q&A settings.", - "security": [ - { - "sessionCookie": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" + }, + "KbProfile": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "format": "uuid" + }, + "tagline": { + "type": "string", + "nullable": true + }, + "about": { + "type": "string", + "nullable": true + }, + "trustSignals": { + "type": "string", + "nullable": true + }, + "serviceCoverage": { + "type": "string", + "nullable": true + }, + "answerStyle": { + "type": "string", + "nullable": true } - ], - "responses": { - "200": { - "description": "Full deck data", + } + }, + "KbDocument": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "tenantId": { + "type": "string", + "format": "uuid" + }, + "filename": { + "type": "string" + }, + "displayName": { + "type": "string", + "nullable": true + }, + "contentType": { + "type": "string" + }, + "sizeBytes": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "uploading", + "pending", + "processing", + "ready", + "failed" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "tenantId", + "filename", + "contentType", + "sizeBytes", + "status", + "createdAt" + ] + }, + "ActionConfig": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "actionType": { + "type": "string" + }, + "templateMarkdown": { + "type": "string" + }, + "disclosuresMarkdown": { + "type": "string", + "nullable": true + }, + "requirementsJson": { + "type": "object", + "nullable": true + }, + "repName": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "deckId", + "actionType", + "templateMarkdown", + "status", + "createdAt", + "updatedAt" + ] + }, + "IntroConfig": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "backgroundImage": { + "type": "string", + "description": "S3 key or URL for intro background image" + }, + "headline": { + "type": "string" + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "ctaButtonLabel": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "deckId", + "backgroundImage", + "headline" + ] + }, + "NotificationSubscription": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "eventType": { + "type": "string" + }, + "channel": { + "type": "string", + "enum": [ + "webhook", + "email", + "console" + ] + }, + "channelConfig": { + "type": "object", + "description": "Channel-specific configuration (e.g. URL for webhook, recipients for email)" + }, + "deckId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "isActive": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "organizationId", + "name", + "eventType", + "channel", + "channelConfig", + "isActive", + "createdAt", + "updatedAt" + ] + }, + "EventLogEntry": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "eventType": { + "type": "string" + }, + "aggregateType": { + "type": "string" + }, + "aggregateId": { + "type": "string" + }, + "payload": { + "type": "object" + }, + "occurredAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "eventType", + "aggregateType", + "aggregateId", + "occurredAt" + ] + }, + "ProductSlideData": { + "type": "object", + "description": "Good/better/best product pricing tiers for a product pricing slide.", + "properties": { + "good": { + "type": "object", + "nullable": true + }, + "better": { + "type": "object", + "nullable": true + }, + "best": { + "type": "object", + "nullable": true + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "brandLogo": { + "type": "string", + "nullable": true + }, + "disclaimer": { + "type": "string", + "nullable": true + }, + "defaultSelectedTier": { + "type": "string", + "enum": [ + "good", + "better", + "best" + ], + "nullable": true + } + } + }, + "Font": { + "type": "object", + "properties": { + "family": { + "type": "string", + "example": "Inter" + }, + "variants": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "type": "string", + "example": "sans-serif" + } + }, + "required": [ + "family", + "variants", + "category" + ] + } + } + }, + "paths": { + "/api/decks": { + "get": { + "tags": [ + "Decks" + ], + "summary": "List decks", + "description": "Returns all decks for the authenticated organization, ordered by most recently created.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "List of decks", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "decks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeckSummary" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Decks" + ], + "summary": "Create a deck", + "security": [ + { + "sessionCookie": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "structured", + "image-based" + ], + "default": "structured" + }, + "deckContext": { + "type": "string", + "description": "Background information to guide AI narration generation (e.g. audience, purpose)" + }, + "voiceProvider": { + "type": "string", + "enum": [ + "elevenlabs", + "openai" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Deck created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deck": { + "$ref": "#/components/schemas/Deck" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}": { + "get": { + "tags": [ + "Decks" + ], + "summary": "Get a deck", + "description": "Returns the full deck including slides, FAQs, branding, and Q&A settings.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "Full deck data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deck": { + "$ref": "#/components/schemas/Deck" + }, + "slides": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Slide" + } + }, + "faqs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Faq" + } + }, + "branding": { + "nullable": true, + "$ref": "#/components/schemas/Branding" + }, + "qaSettings": { + "nullable": true, + "$ref": "#/components/schemas/QaSettings" + }, + "presentationsCount": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Decks" + ], + "summary": "Update a deck", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "structured", + "image-based" + ] + }, + "isActive": { + "type": "boolean" + }, + "deckContext": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated deck", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deck": { + "$ref": "#/components/schemas/Deck" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Decks" + ], + "summary": "Delete a deck", + "description": "Permanently deletes a deck and all associated slides, FAQs, and branding.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "Deck deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/slides": { + "get": { + "tags": [ + "Slides" + ], + "summary": "List slides", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "Ordered list of slides", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slides": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Slide" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Slides" + ], + "summary": "Create a slide", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "slideId", + "title", + "narrationScript" + ], + "properties": { + "slideId": { + "type": "string", + "description": "Human-readable identifier (e.g. \"slide-1\")" + }, + "title": { + "type": "string" + }, + "bullets": { + "type": "array", + "items": { + "type": "string" + } + }, + "narrationScript": { + "type": "string" + }, + "slideContext": { + "type": "string", + "nullable": true + }, + "audioUrl": { + "type": "string", + "description": "S3 key for pre-recorded audio" + }, + "heroImg": { + "type": "string", + "nullable": true, + "description": "S3 key for slide image" + }, + "orderIndex": { + "type": "integer" + }, + "slideType": { + "type": "string", + "enum": [ + "standard", + "product_pricing_v1" + ], + "default": "standard" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Slide created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slide": { + "$ref": "#/components/schemas/Slide" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/slides/reorder": { + "put": { + "tags": [ + "Slides" + ], + "summary": "Reorder slides", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "slideIds" + ], + "properties": { + "slideIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Full ordered array of slide UUIDs. Position in array becomes the new orderIndex." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Slides reordered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/slides/{slideId}": { + "put": { + "tags": [ + "Slides" + ], + "summary": "Update a slide", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "slideId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the slide" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "bullets": { + "type": "array", + "items": { + "type": "string" + } + }, + "narrationScript": { + "type": "string" + }, + "slideContext": { + "type": "string", + "nullable": true + }, + "audioUrl": { + "type": "string" + }, + "heroImg": { + "type": "string", + "nullable": true + }, + "orderIndex": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated slide", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slide": { + "$ref": "#/components/schemas/Slide" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Slides" + ], + "summary": "Delete a slide", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "slideId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the slide" + } + ], + "responses": { + "200": { + "description": "Slide deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/faqs": { + "get": { + "tags": [ + "FAQs" + ], + "summary": "List FAQs", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "FAQs with pre-generated audio", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "faqs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Faq" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "FAQs" + ], + "summary": "Create a FAQ", + "description": "Creates a FAQ and automatically generates TTS audio for the answer.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "userQuestion", + "cannedAnswer" + ], + "properties": { + "userQuestion": { + "type": "string" + }, + "cannedAnswer": { + "type": "string" + }, + "relatedSlideId": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + } + } + } + }, + "responses": { + "201": { + "description": "FAQ created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "faq": { + "$ref": "#/components/schemas/Faq" + }, + "warning": { + "type": "string", + "description": "Present if audio generation failed (FAQ is still saved)" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/faqs/{faqId}": { + "put": { + "tags": [ + "FAQs" + ], + "summary": "Update a FAQ", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "faqId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the FAQ" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userQuestion": { + "type": "string" + }, + "cannedAnswer": { + "type": "string" + }, + "relatedSlideId": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated FAQ", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "faq": { + "$ref": "#/components/schemas/Faq" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "FAQs" + ], + "summary": "Delete a FAQ", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "faqId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the FAQ" + } + ], + "responses": { + "200": { + "description": "FAQ deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/branding": { + "get": { + "tags": [ + "Branding" + ], + "summary": "Get branding", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "Branding configuration, or null if not yet set", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branding": { + "nullable": true, + "$ref": "#/components/schemas/Branding" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Branding" + ], + "summary": "Upsert branding", + "description": "Creates or replaces the branding configuration for a deck.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "palette", + "cta" + ], + "properties": { + "name": { + "type": "string" + }, + "logo": { + "type": "string", + "nullable": true, + "description": "S3 key for logo image" + }, + "avatar": { + "type": "string", + "nullable": true, + "description": "S3 key for rep avatar image" + }, + "chatName": { + "type": "string", + "nullable": true + }, + "chatGreeting": { + "type": "string", + "nullable": true + }, + "palette": { + "$ref": "#/components/schemas/BrandingPalette" + }, + "cta": { + "$ref": "#/components/schemas/BrandingCta" + }, + "fonts": { + "type": "object" + }, + "contact": { + "type": "object", + "properties": { + "phone": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Saved branding", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branding": { + "$ref": "#/components/schemas/Branding" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/qa-settings": { + "get": { + "tags": [ + "Q&A" + ], + "summary": "Get Q&A settings", + "description": "Returns the customizable resume and transition prompts for the deck's Q&A experience.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "Q&A settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "qaSettings": { + "nullable": true, + "$ref": "#/components/schemas/QaSettings" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Q&A" + ], + "summary": "Update Q&A settings", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "resumePromptText": { + "type": "string" + }, + "resumeVoiceId": { + "type": "string" + }, + "transitionPromptText": { + "type": "string" + }, + "transitionVoiceId": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated Q&A settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "qaSettings": { + "$ref": "#/components/schemas/QaSettings" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/generate": { + "post": { + "tags": [ + "Generation" + ], + "summary": "Start AI generation from images", + "description": "Accepts uploaded slide images, runs them through GPT-4o Vision to extract content, generates narration scripts, and synthesizes audio. Returns a job ID — poll `/api/decks/{deckId}/generate/status` for progress.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "images" + ], + "properties": { + "images": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "description": "Slide images (PNG, JPG, or WebP; max 10 MB each)" + } + } + } + } + } + }, + "responses": { + "202": { + "description": "Job accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/generate/status": { + "get": { + "tags": [ + "Generation" + ], + "summary": "Poll generation status", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "Current job status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerationStatus" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/decks/{deckId}/presentations": { + "get": { + "tags": [ + "Presentations" + ], + "summary": "List presentations", + "description": "Returns all non-deleted presentations created from this deck.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "responses": { + "200": { + "description": "List of presentations", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "presentations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Presentation" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Presentations" + ], + "summary": "Create a presentation", + "description": "Creates a personalized presentation for a homeowner. Accepts session cookie or Bearer token authentication (for programmatic integrations such as CRM workflows). The address is validated via Google Geocoding. Returns a shareable URL immediately; audio generation may continue in the background.", + "security": [ + { + "sessionCookie": [] + }, + { + "bearerToken": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "deckId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the deck" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "homeowner" + ], + "properties": { + "homeowner": { + "$ref": "#/components/schemas/HomeownerData" + }, + "voice_settings": { + "type": "object" + }, + "language_settings": { + "type": "object" + }, + "price_inputs": { + "type": "object", + "additionalProperties": { + "type": "number" + }, + "description": "Values for deck-level price variables (used to evaluate formulas)" + }, + "measurement_strategy": { + "type": "string", + "enum": [ + "manual", + "auto" + ], + "default": "manual" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Presentation created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deck_presentation_id": { + "type": "string", + "format": "uuid" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Shareable presentation URL" + }, + "status": { + "type": "string", + "enum": [ + "not_started", + "pending", + "processing", + "ready", + "failed" + ] + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "422": { + "description": "Unprocessable entity", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/presentations/{presentationId}": { + "patch": { + "tags": [ + "Presentations" + ], + "summary": "Update a presentation", + "description": "Partial update for homeowner data, voice settings, language, or price inputs.", + "parameters": [ + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the presentation" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "homeownerData": { + "type": "object" + }, + "voiceSettings": { + "type": "object", + "nullable": true + }, + "languageSettings": { + "type": "object", + "nullable": true + }, + "priceInputs": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Presentation updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "presentationId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Presentations" + ], + "summary": "Delete a presentation", + "description": "Soft-deletes a presentation (sets `deletedAt`). The shareable URL stops working immediately.", + "parameters": [ + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the presentation" + } + ], + "responses": { + "200": { + "description": "Presentation deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "presentationId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/presentations/{presentationId}/status": { + "get": { + "tags": [ + "Presentations" + ], + "summary": "Get presentation status", + "description": "Returns the current processing status and slide readiness.", + "parameters": [ + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the presentation" + } + ], + "responses": { + "200": { + "description": "Presentation status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "not_started", + "pending", + "processing", + "ready", + "failed" + ] + }, + "slidesReady": { + "type": "integer" + }, + "totalSlides": { + "type": "integer" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/qa": { + "post": { + "tags": [ + "Q&A" + ], + "summary": "Answer a question", + "description": "Answers a viewer question. Checks FAQs first (exact then semantic match at >= 0.6 confidence), falls back to GPT-4o with slide context. Returns answer text plus synthesized audio.\n\n**Authentication**: Accepts either a session cookie (admin) or a `presentationId` (public viewer).", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "question", + "slideId" + ], + "properties": { + "question": { + "type": "string", + "minLength": 3 + }, + "slideId": { + "type": "string", + "description": "Current slide ID for context" + }, + "deckId": { + "type": "string", + "format": "uuid" + }, + "presentationId": { + "type": "string", + "format": "uuid", + "description": "Required for unauthenticated viewer access" + }, + "history": { + "type": "array", + "items": { + "type": "object", + "required": [ + "role", + "content" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "user", + "assistant" + ] + }, + "content": { + "type": "string" + } + } + } + }, + "voiceId": { + "type": "string" + }, + "language": { + "type": "string", + "example": "en", + "description": "BCP-47 language tag" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Answer with audio", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QaResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/narration/single": { + "post": { + "tags": [ + "Narration" + ], + "summary": "Synthesize narration for one slide", + "description": "Generates TTS audio for a slide script. Results are cached per session.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "sessionId", + "slideId", + "script" + ], + "properties": { + "sessionId": { + "type": "string", + "description": "Unique session identifier for caching" + }, + "slideId": { + "type": "string" + }, + "script": { + "type": "string", + "minLength": 1 + }, + "voiceId": { + "type": "string" + }, + "presentationId": { + "type": "string", + "format": "uuid", + "description": "Required for unauthenticated viewer access" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Audio URL and word timings", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NarrationResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/narration/batch": { + "post": { + "tags": [ + "Narration" + ], + "summary": "Synthesize narration for multiple slides", + "description": "Batch TTS generation. Per-slide failures return a fallback audio URL rather than failing the whole batch.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "sessionId", + "slides" + ], + "properties": { + "sessionId": { + "type": "string" + }, + "slides": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "slideId", + "script" + ], + "properties": { + "slideId": { + "type": "string" + }, + "script": { + "type": "string", + "minLength": 1 + }, + "voiceId": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Narration responses for each slide", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cachedIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "responses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NarrationResponse" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/narration/resume": { + "post": { + "tags": [ + "Narration" + ], + "summary": "Synthesize resume narration", + "description": "Generates the spoken bridge from Q&A back into the presentation, using the deck's configured transition prompt.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deckId": { + "type": "string", + "format": "uuid" + }, + "presentationId": { + "type": "string", + "format": "uuid" + }, + "voiceId": { + "type": "string" + }, + "language": { + "type": "string", + "example": "en" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Resume narration audio", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NarrationResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/images": { + "get": { + "tags": [ + "Images" + ], + "summary": "List images", + "description": "Returns all uploaded images for the authenticated organization.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "List of images", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Image" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/images/upload": { + "post": { + "tags": [ + "Images" + ], + "summary": "Upload an image", + "description": "Uploads and optimizes an image (max 1920px width, JPEG quality 85). Returns the S3 key and a presigned URL.", + "security": [ + { + "sessionCookie": [] + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "Image file (PNG, JPG, or WebP; max 10 MB)" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Uploaded image metadata", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "image": { + "$ref": "#/components/schemas/Image" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/images/{imageId}": { + "delete": { + "tags": [ + "Images" + ], + "summary": "Delete an image", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "imageId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "UUID of the image" + } + ], + "responses": { + "200": { + "description": "Image deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/voices/list": { + "get": { + "tags": [ + "Voices" + ], + "summary": "List available voices", + "description": "Returns a curated list of voices suitable for sales presentations.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "Voice list with preview URLs", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "voices": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Voice" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/health": { + "get": { + "tags": [ + "Utility" + ], + "summary": "Health check", + "description": "Returns `{ ok: true }` if the service is running.", + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "/api/auth/login": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Log in", + "description": "Authenticates with email and password. Returns user and organization info and sets a session cookie. Rate-limited to prevent brute-force attacks.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "rep@acmesolar.com" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Login successful. A `session` cookie is set on the response.", "content": { "application/json": { "schema": { "type": "object", "properties": { - "deck": { - "$ref": "#/components/schemas/Deck" - }, - "slides": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Slide" - } - }, - "faqs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Faq" - } - }, - "branding": { - "$ref": "#/components/schemas/Branding", - "nullable": true + "user": { + "$ref": "#/components/schemas/User" }, - "qaSettings": { - "$ref": "#/components/schemas/QaSettings", - "nullable": true + "organization": { + "$ref": "#/components/schemas/Organization" + } + } + } + } + } + }, + "400": { + "description": "Missing email or password", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/auth/logout": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Log out", + "description": "Revokes the current session and clears the session cookie.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "Logged out", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "/api/auth/me": { + "get": { + "tags": [ + "Authentication" + ], + "summary": "Get current user", + "description": "Returns the authenticated user's identity and organization.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "Current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/User" }, - "presentationsCount": { - "type": "integer" + "organization": { + "$ref": "#/components/schemas/Organization" } } } @@ -1023,8 +4432,52 @@ } } }, - "404": { - "description": "Not found", + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/company/contact": { + "get": { + "tags": [ + "Company" + ], + "summary": "Get company contact info", + "description": "Returns the organization's contact details used in contracts and branding.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "Company contact", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contact": { + "$ref": "#/components/schemas/CompanyContact" + }, + "organizationName": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -1047,24 +4500,122 @@ }, "put": { "tags": [ - "Decks" + "Company" ], - "summary": "Update a deck", + "summary": "Update company contact info", "security": [ { "sessionCookie": [] } ], - "parameters": [ + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompanyContact" + } + } + } + }, + "responses": { + "200": { + "description": "Updated contact", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contact": { + "$ref": "#/components/schemas/CompanyContact" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/company/kb/profile": { + "get": { + "tags": [ + "Company" + ], + "summary": "Get knowledge base profile", + "description": "Returns the company's knowledge base profile that guides how the AI answers customer questions.", + "security": [ { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "KB profile", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "profile": { + "$ref": "#/components/schemas/KbProfile" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "put": { + "tags": [ + "Company" + ], + "summary": "Update knowledge base profile", + "description": "Updates the company tagline, about text, trust signals, service coverage, and answer style used by the AI.", + "security": [ + { + "sessionCookie": [] } ], "requestBody": { @@ -1074,26 +4625,20 @@ "schema": { "type": "object", "properties": { - "name": { + "tagline": { "type": "string" }, - "description": { - "type": "string", - "nullable": true + "about": { + "type": "string" }, - "type": { - "type": "string", - "enum": [ - "structured", - "image-based" - ] + "trustSignals": { + "type": "string" }, - "isActive": { - "type": "boolean" + "serviceCoverage": { + "type": "string" }, - "deckContext": { - "type": "string", - "nullable": true + "answerStyle": { + "type": "string" } } } @@ -1102,22 +4647,22 @@ }, "responses": { "200": { - "description": "Updated deck", + "description": "Updated profile", "content": { "application/json": { "schema": { "type": "object", "properties": { - "deck": { - "$ref": "#/components/schemas/Deck" + "profile": { + "$ref": "#/components/schemas/KbProfile" } } } } } }, - "400": { - "description": "Bad request", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -1126,8 +4671,8 @@ } } }, - "401": { - "description": "Unauthorized", + "500": { + "description": "Internal server error", "content": { "application/json": { "schema": { @@ -1135,9 +4680,57 @@ } } } + } + } + } + }, + "/api/company/kb/documents": { + "get": { + "tags": [ + "Company" + ], + "summary": "List knowledge base documents", + "description": "Returns all uploaded knowledge base documents for the organization, with usage metadata.", + "security": [ + { + "sessionCookie": [] + } + ], + "responses": { + "200": { + "description": "KB documents", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "documents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KbDocument" + } + }, + "meta": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "remaining": { + "type": "integer" + } + } + } + } + } + } + } }, - "404": { - "description": "Not found", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -1157,48 +4750,83 @@ } } } - }, - "delete": { + } + }, + "/api/company/kb/documents/upload-init": { + "post": { "tags": [ - "Decks" + "Company" ], - "summary": "Delete a deck", - "description": "Permanently deletes a deck and all associated slides, FAQs, and branding.", + "summary": "Initiate document upload", + "description": "Validates the file and returns a presigned S3 URL for direct upload. Call `/api/company/kb/documents/complete` after the upload finishes.", "security": [ { "sessionCookie": [] } ], - "parameters": [ - { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "filename", + "contentType", + "sizeBytes" + ], + "properties": { + "filename": { + "type": "string", + "example": "product-guide.pdf" + }, + "contentType": { + "type": "string", + "example": "application/pdf" + }, + "sizeBytes": { + "type": "integer", + "example": 1048576 + }, + "displayName": { + "type": "string" + } + } + } + } } - ], + }, "responses": { "200": { - "description": "Deck deleted", + "description": "Upload initiated", "content": { "application/json": { "schema": { "type": "object", "properties": { - "success": { - "type": "boolean" + "documentId": { + "type": "string", + "format": "uuid" + }, + "uploadUrl": { + "type": "string", + "format": "uri", + "description": "Presigned S3 URL for PUT upload" + }, + "storageKey": { + "type": "string" + }, + "expiresIn": { + "type": "integer", + "description": "URL expiry in seconds" } } } } } }, - "401": { - "description": "Unauthorized", + "400": { + "description": "Bad request (invalid file type, size, or document limit reached)", "content": { "application/json": { "schema": { @@ -1207,8 +4835,8 @@ } } }, - "404": { - "description": "Not found", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -1230,48 +4858,68 @@ } } }, - "/api/decks/{deckId}/slides": { - "get": { + "/api/company/kb/documents/complete": { + "post": { "tags": [ - "Slides" + "Company" ], - "summary": "List slides", + "summary": "Complete document upload", + "description": "Marks a document as uploaded after the client finishes uploading to S3. Triggers background processing (text extraction, chunking, and embedding).", "security": [ { "sessionCookie": [] } ], - "parameters": [ - { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "documentId" + ], + "properties": { + "documentId": { + "type": "string", + "format": "uuid" + } + } + } + } } - ], + }, "responses": { "200": { - "description": "Ordered list of slides", + "description": "Upload marked as complete", "content": { "application/json": { "schema": { "type": "object", "properties": { - "slides": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Slide" - } + "status": { + "type": "string", + "example": "pending" + }, + "documentId": { + "type": "string", + "format": "uuid" } } } } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "401": { "description": "Unauthorized", "content": { @@ -1283,7 +4931,7 @@ } }, "404": { - "description": "Not found", + "description": "Document not found", "content": { "application/json": { "schema": { @@ -1303,12 +4951,14 @@ } } } - }, - "post": { + } + }, + "/api/company/kb/documents/{documentId}": { + "get": { "tags": [ - "Slides" + "Company" ], - "summary": "Create a slide", + "summary": "Get a knowledge base document", "security": [ { "sessionCookie": [] @@ -1317,98 +4967,30 @@ "parameters": [ { "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "slideId", - "title", - "narrationScript" - ], - "properties": { - "slideId": { - "type": "string", - "description": "Human-readable identifier (e.g. \"slide-1\")" - }, - "title": { - "type": "string" - }, - "bullets": { - "type": "array", - "items": { - "type": "string" - } - }, - "narrationScript": { - "type": "string" - }, - "slideContext": { - "type": "string", - "nullable": true - }, - "audioUrl": { - "type": "string", - "description": "S3 key for pre-recorded audio" - }, - "heroImg": { - "type": "string", - "nullable": true, - "description": "S3 key for slide image" - }, - "orderIndex": { - "type": "integer" - }, - "slideType": { - "type": "string", - "enum": [ - "standard", - "product_pricing_v1" - ], - "default": "standard" - } - } - } + "name": "documentId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } - }, + ], "responses": { - "201": { - "description": "Slide created", + "200": { + "description": "Document details", "content": { "application/json": { "schema": { "type": "object", "properties": { - "slide": { - "$ref": "#/components/schemas/Slide" + "document": { + "$ref": "#/components/schemas/KbDocument" } } } } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "401": { "description": "Unauthorized", "content": { @@ -1440,14 +5022,13 @@ } } } - } - }, - "/api/decks/{deckId}/slides/reorder": { - "put": { + }, + "delete": { "tags": [ - "Slides" + "Company" ], - "summary": "Reorder slides", + "summary": "Delete a knowledge base document", + "description": "Removes the document from S3 and the database. Associated chunks are deleted automatically.", "security": [ { "sessionCookie": [] @@ -1456,41 +5037,17 @@ "parameters": [ { "in": "path", - "name": "deckId", + "name": "documentId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "slideIds" - ], - "properties": { - "slideIds": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "description": "Full ordered array of slide UUIDs. Position in array becomes the new orderIndex." - } - } - } } } - }, + ], "responses": { "200": { - "description": "Slides reordered", + "description": "Document deleted", "content": { "application/json": { "schema": { @@ -1504,16 +5061,6 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "401": { "description": "Unauthorized", "content": { @@ -1547,61 +5094,32 @@ } } }, - "/api/slides/{slideId}": { - "put": { + "/api/contracts/import": { + "post": { "tags": [ - "Slides" + "Contracts" ], - "summary": "Update a slide", + "summary": "Import a contract document", + "description": "Uploads a contract document (PDF or Word), extracts text, and uses AI to analyze it for template conversion. Returns detected variables that can be used in contract templates.", "security": [ { "sessionCookie": [] } ], - "parameters": [ - { - "in": "path", - "name": "slideId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the slide" - } - ], "requestBody": { "required": true, "content": { - "application/json": { + "multipart/form-data": { "schema": { "type": "object", + "required": [ + "file" + ], "properties": { - "title": { - "type": "string" - }, - "bullets": { - "type": "array", - "items": { - "type": "string" - } - }, - "narrationScript": { - "type": "string" - }, - "slideContext": { - "type": "string", - "nullable": true - }, - "audioUrl": { - "type": "string" - }, - "heroImg": { + "file": { "type": "string", - "nullable": true - }, - "orderIndex": { - "type": "integer" + "format": "binary", + "description": "Contract file (PDF or DOCX, max 10 MB)" } } } @@ -1610,14 +5128,17 @@ }, "responses": { "200": { - "description": "Updated slide", + "description": "Analyzed contract", "content": { "application/json": { "schema": { "type": "object", "properties": { - "slide": { - "$ref": "#/components/schemas/Slide" + "detectedVariables": { + "type": "array", + "items": { + "type": "string" + } } } } @@ -1625,7 +5146,7 @@ } }, "400": { - "description": "Bad request", + "description": "Bad request (unsupported file type or too large)", "content": { "application/json": { "schema": { @@ -1644,16 +5165,6 @@ } } }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -1665,12 +5176,15 @@ } } } - }, - "delete": { + } + }, + "/api/decks/{deckId}/action-configs": { + "get": { "tags": [ - "Slides" + "Contracts" ], - "summary": "Delete a slide", + "summary": "List action configs", + "description": "Returns all action configurations (contract templates) for a deck.", "security": [ { "sessionCookie": [] @@ -1679,26 +5193,23 @@ "parameters": [ { "in": "path", - "name": "slideId", + "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the slide" + } } ], "responses": { "200": { - "description": "Slide deleted", + "description": "Action configs", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } + "type": "array", + "items": { + "$ref": "#/components/schemas/ActionConfig" } } } @@ -1735,14 +5246,13 @@ } } } - } - }, - "/api/decks/{deckId}/faqs": { - "get": { + }, + "post": { "tags": [ - "FAQs" + "Contracts" ], - "summary": "List FAQs", + "summary": "Create an action config", + "description": "Creates a new contract template or action configuration for a deck.", "security": [ { "sessionCookie": [] @@ -1756,25 +5266,66 @@ "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" + } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "actionType", + "templateMarkdown" + ], + "properties": { + "actionType": { + "type": "string", + "example": "loi" + }, + "templateMarkdown": { + "type": "string" + }, + "disclosuresMarkdown": { + "type": "string" + }, + "requirementsJson": { + "type": "object" + }, + "repName": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "default": "draft" + } + } + } + } + } + }, "responses": { - "200": { - "description": "FAQs with pre-generated audio", + "201": { + "description": "Action config created", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "faqs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Faq" - } - } - } + "$ref": "#/components/schemas/ActionConfig" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" } } } @@ -1810,13 +5361,14 @@ } } } - }, - "post": { + } + }, + "/api/decks/{deckId}/action-configs/{configId}": { + "get": { "tags": [ - "FAQs" + "Contracts" ], - "summary": "Create a FAQ", - "description": "Creates a FAQ and automatically generates TTS audio for the answer.", + "summary": "Get an action config", "security": [ { "sessionCookie": [] @@ -1830,63 +5382,25 @@ "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "userQuestion", - "cannedAnswer" - ], - "properties": { - "userQuestion": { - "type": "string" - }, - "cannedAnswer": { - "type": "string" - }, - "relatedSlideId": { - "type": "string", - "format": "uuid", - "nullable": true - } - } - } + } + }, + { + "in": "path", + "name": "configId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } - }, + ], "responses": { - "201": { - "description": "FAQ created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "faq": { - "$ref": "#/components/schemas/Faq" - }, - "warning": { - "type": "string", - "description": "Present if audio generation failed (FAQ is still saved)" - } - } - } - } - } - }, - "400": { - "description": "Bad request", + "200": { + "description": "Action config", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/ActionConfig" } } } @@ -1922,14 +5436,12 @@ } } } - } - }, - "/api/faqs/{faqId}": { + }, "put": { "tags": [ - "FAQs" + "Contracts" ], - "summary": "Update a FAQ", + "summary": "Update an action config", "security": [ { "sessionCookie": [] @@ -1938,13 +5450,21 @@ "parameters": [ { "in": "path", - "name": "faqId", + "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the FAQ" + } + }, + { + "in": "path", + "name": "configId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } } ], "requestBody": { @@ -1954,16 +5474,24 @@ "schema": { "type": "object", "properties": { - "userQuestion": { + "templateMarkdown": { "type": "string" }, - "cannedAnswer": { + "disclosuresMarkdown": { "type": "string" }, - "relatedSlideId": { + "requirementsJson": { + "type": "object" + }, + "repName": { + "type": "string" + }, + "status": { "type": "string", - "format": "uuid", - "nullable": true + "enum": [ + "draft", + "published" + ] } } } @@ -1972,16 +5500,11 @@ }, "responses": { "200": { - "description": "Updated FAQ", + "description": "Updated action config", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "faq": { - "$ref": "#/components/schemas/Faq" - } - } + "$ref": "#/components/schemas/ActionConfig" } } } @@ -2030,9 +5553,9 @@ }, "delete": { "tags": [ - "FAQs" + "Contracts" ], - "summary": "Delete a FAQ", + "summary": "Delete an action config", "security": [ { "sessionCookie": [] @@ -2041,18 +5564,26 @@ "parameters": [ { "in": "path", - "name": "faqId", + "name": "deckId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the FAQ" + } + }, + { + "in": "path", + "name": "configId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { "200": { - "description": "FAQ deleted", + "description": "Action config deleted", "content": { "application/json": { "schema": { @@ -2099,17 +5630,13 @@ } } }, - "/api/decks/{deckId}/branding": { + "/api/decks/{deckId}/intro": { "get": { "tags": [ - "Branding" - ], - "summary": "Get branding", - "security": [ - { - "sessionCookie": [] - } + "Decks" ], + "summary": "Get intro page config", + "description": "Returns the intro page configuration for a deck, including the background image, headline, and CTA button label.", "parameters": [ { "in": "path", @@ -2118,37 +5645,26 @@ "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" + } } ], "responses": { "200": { - "description": "Branding configuration, or null if not yet set", + "description": "Intro configuration, or null if not set", "content": { "application/json": { "schema": { "type": "object", "properties": { - "branding": { - "$ref": "#/components/schemas/Branding", - "nullable": true + "intro": { + "nullable": true, + "$ref": "#/components/schemas/IntroConfig" } } } } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "404": { "description": "Not found", "content": { @@ -2173,10 +5689,10 @@ }, "put": { "tags": [ - "Branding" + "Decks" ], - "summary": "Upsert branding", - "description": "Creates or replaces the branding configuration for a deck.", + "summary": "Upsert intro page config", + "description": "Creates or updates the intro page for a deck. Enabling an intro page switches the deck to lazy generation mode.", "security": [ { "sessionCookie": [] @@ -2190,8 +5706,7 @@ "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" + } } ], "requestBody": { @@ -2201,51 +5716,24 @@ "schema": { "type": "object", "required": [ - "palette", - "cta" + "backgroundImage", + "headline" ], "properties": { - "name": { - "type": "string" - }, - "logo": { + "backgroundImage": { "type": "string", - "nullable": true, - "description": "S3 key for logo image" + "description": "S3 key for the intro background image" }, - "avatar": { + "headline": { "type": "string", - "nullable": true, - "description": "S3 key for rep avatar image" + "example": "Your Custom Solar Proposal" }, - "chatName": { - "type": "string", - "nullable": true + "subtitle": { + "type": "string" }, - "chatGreeting": { + "ctaButtonLabel": { "type": "string", - "nullable": true - }, - "palette": { - "$ref": "#/components/schemas/BrandingPalette" - }, - "cta": { - "$ref": "#/components/schemas/BrandingCta" - }, - "fonts": { - "type": "object" - }, - "contact": { - "type": "object", - "properties": { - "phone": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - } - } + "example": "View My Proposal" } } } @@ -2254,14 +5742,14 @@ }, "responses": { "200": { - "description": "Saved branding", + "description": "Saved intro config", "content": { "application/json": { "schema": { "type": "object", "properties": { - "branding": { - "$ref": "#/components/schemas/Branding" + "intro": { + "$ref": "#/components/schemas/IntroConfig" } } } @@ -2278,26 +5766,6 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -2309,15 +5777,12 @@ } } } - } - }, - "/api/decks/{deckId}/qa-settings": { - "get": { + }, + "delete": { "tags": [ - "Q&A" + "Decks" ], - "summary": "Get Q&A settings", - "description": "Returns the customizable resume and transition prompts for the deck's Q&A experience.", + "summary": "Delete intro page config", "security": [ { "sessionCookie": [] @@ -2331,47 +5796,25 @@ "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" + } } ], "responses": { "200": { - "description": "Q&A settings", + "description": "Intro deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { - "qaSettings": { - "$ref": "#/components/schemas/QaSettings", - "nullable": true + "success": { + "type": "boolean" } } } } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -2382,80 +5825,48 @@ } } } - } - }, - "put": { - "tags": [ - "Q&A" - ], - "summary": "Update Q&A settings", - "security": [ - { - "sessionCookie": [] - } - ], - "parameters": [ - { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "resumePromptText": { - "type": "string" - }, - "resumeVoiceId": { - "type": "string" - }, - "transitionPromptText": { - "type": "string" - }, - "transitionVoiceId": { - "type": "string" - } - } - } - } - } - }, + } + } + }, + "/api/slides/{slideId}/product-data": { + "get": { + "tags": [ + "Slides" + ], + "summary": "Get product pricing data", + "description": "Returns the good/better/best product pricing tiers for a product pricing slide.", + "security": [ + { + "sessionCookie": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "slideId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], "responses": { "200": { - "description": "Updated Q&A settings", + "description": "Product data", "content": { "application/json": { "schema": { "type": "object", "properties": { - "qaSettings": { - "$ref": "#/components/schemas/QaSettings" + "productData": { + "$ref": "#/components/schemas/ProductSlideData" } } } } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "401": { "description": "Unauthorized", "content": { @@ -2487,15 +5898,13 @@ } } } - } - }, - "/api/decks/{deckId}/generate": { - "post": { + }, + "put": { "tags": [ - "Generation" + "Slides" ], - "summary": "Start AI generation from images", - "description": "Accepts uploaded slide images, runs them through GPT-4o Vision to extract content, generates narration scripts, and synthesizes audio. Returns a job ID — poll `/api/decks/{deckId}/generate/status` for progress.", + "summary": "Upsert product pricing data", + "description": "Sets product pricing tiers for a slide. The slide type is automatically set to `product_pricing_v1`.", "security": [ { "sessionCookie": [] @@ -2504,32 +5913,26 @@ "parameters": [ { "in": "path", - "name": "deckId", + "name": "slideId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" + } } ], "requestBody": { "required": true, "content": { - "multipart/form-data": { + "application/json": { "schema": { "type": "object", "required": [ - "images" + "productData" ], "properties": { - "images": { - "type": "array", - "items": { - "type": "string", - "format": "binary" - }, - "description": "Slide images (PNG, JPG, or WebP; max 10 MB each)" + "productData": { + "$ref": "#/components/schemas/ProductSlideData" } } } @@ -2537,16 +5940,15 @@ } }, "responses": { - "202": { - "description": "Job accepted", + "200": { + "description": "Product data saved", "content": { "application/json": { "schema": { "type": "object", "properties": { - "jobId": { - "type": "string", - "format": "uuid" + "success": { + "type": "boolean" } } } @@ -2594,14 +5996,13 @@ } } } - } - }, - "/api/decks/{deckId}/generate/status": { - "get": { + }, + "delete": { "tags": [ - "Generation" + "Slides" ], - "summary": "Poll generation status", + "summary": "Remove product pricing data", + "description": "Removes product data and resets the slide type to `standard`.", "security": [ { "sessionCookie": [] @@ -2610,22 +6011,26 @@ "parameters": [ { "in": "path", - "name": "deckId", + "name": "slideId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the deck" + } } ], "responses": { "200": { - "description": "Current job status", + "description": "Product data removed", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GenerationStatus" + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } } } } @@ -2663,44 +6068,27 @@ } } }, - "/api/decks/{deckId}/presentations": { + "/api/notifications/subscriptions": { "get": { "tags": [ - "Presentations" + "Notifications" ], - "summary": "List presentations", - "description": "Returns all non-deleted presentations created from this deck.", + "summary": "List notification subscriptions", + "description": "Returns all notification subscriptions for the organization.", "security": [ { "sessionCookie": [] } ], - "parameters": [ - { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" - } - ], "responses": { "200": { - "description": "List of presentations", + "description": "Subscriptions", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "presentations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Presentation" - } - } + "type": "array", + "items": { + "$ref": "#/components/schemas/NotificationSubscription" } } } @@ -2716,16 +6104,6 @@ } } }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -2740,27 +6118,15 @@ }, "post": { "tags": [ - "Presentations" + "Notifications" ], - "summary": "Create a presentation", - "description": "Creates a personalized presentation for a homeowner. The address is validated via Google Geocoding. Returns a shareable URL immediately; audio generation may continue in the background.", + "summary": "Create a notification subscription", + "description": "Subscribes to an event type via a delivery channel (webhook, email, or console).", "security": [ { "sessionCookie": [] } ], - "parameters": [ - { - "in": "path", - "name": "deckId", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - }, - "description": "UUID of the deck" - } - ], "requestBody": { "required": true, "content": { @@ -2768,32 +6134,40 @@ "schema": { "type": "object", "required": [ - "homeowner" + "name", + "eventType", + "channel", + "channelConfig" ], "properties": { - "homeowner": { - "$ref": "#/components/schemas/HomeownerData" + "name": { + "type": "string", + "example": "Slack — new presentation" }, - "voice_settings": { - "type": "object" + "eventType": { + "type": "string", + "example": "presentation.created" }, - "language_settings": { - "type": "object" + "channel": { + "type": "string", + "enum": [ + "webhook", + "email", + "console" + ] }, - "price_inputs": { + "channelConfig": { "type": "object", - "additionalProperties": { - "type": "number" - }, - "description": "Values for deck-level price variables (used to evaluate formulas)" + "description": "Channel-specific config. Webhook: `{ url }`. Email: `{ recipients: [\"a@b.com\"] }`." }, - "measurement_strategy": { + "deckId": { "type": "string", - "enum": [ - "manual", - "auto" - ], - "default": "manual" + "format": "uuid", + "description": "Scope to a specific deck (optional)" + }, + "isActive": { + "type": "boolean", + "default": true } } } @@ -2802,32 +6176,11 @@ }, "responses": { "201": { - "description": "Presentation created", + "description": "Subscription created", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "deck_presentation_id": { - "type": "string", - "format": "uuid" - }, - "url": { - "type": "string", - "format": "uri", - "description": "Shareable presentation URL" - }, - "status": { - "type": "string", - "enum": [ - "not_started", - "pending", - "processing", - "ready", - "failed" - ] - } - } + "$ref": "#/components/schemas/NotificationSubscription" } } } @@ -2852,26 +6205,6 @@ } } }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "422": { - "description": "Unprocessable entity", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, "500": { "description": "Internal server error", "content": { @@ -2885,76 +6218,41 @@ } } }, - "/api/presentations/{presentationId}": { - "patch": { + "/api/notifications/subscriptions/{subscriptionId}": { + "get": { "tags": [ - "Presentations" + "Notifications" + ], + "summary": "Get a notification subscription", + "security": [ + { + "sessionCookie": [] + } ], - "summary": "Update a presentation", - "description": "Partial update for homeowner data, voice settings, language, or price inputs.", "parameters": [ { "in": "path", - "name": "presentationId", + "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the presentation" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "homeownerData": { - "type": "object" - }, - "voiceSettings": { - "type": "object", - "nullable": true - }, - "languageSettings": { - "type": "object", - "nullable": true - }, - "priceInputs": { - "type": "object", - "additionalProperties": { - "type": "number" - } - } - } - } } } - }, + ], "responses": { "200": { - "description": "Presentation updated", + "description": "Subscription", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "presentationId": { - "type": "string", - "format": "uuid" - } - } + "$ref": "#/components/schemas/NotificationSubscription" } } } }, - "400": { - "description": "Bad request", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -2985,40 +6283,71 @@ } } }, - "delete": { + "put": { "tags": [ - "Presentations" + "Notifications" + ], + "summary": "Update a notification subscription", + "security": [ + { + "sessionCookie": [] + } ], - "summary": "Delete a presentation", - "description": "Soft-deletes a presentation (sets `deletedAt`). The shareable URL stops working immediately.", "parameters": [ { "in": "path", - "name": "presentationId", + "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the presentation" + } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "eventType": { + "type": "string" + }, + "channel": { + "type": "string", + "enum": [ + "webhook", + "email", + "console" + ] + }, + "channelConfig": { + "type": "object" + }, + "deckId": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "isActive": { + "type": "boolean" + } + } + } + } + } + }, "responses": { "200": { - "description": "Presentation deleted", + "description": "Updated subscription", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "presentationId": { - "type": "string", - "format": "uuid" - } - } + "$ref": "#/components/schemas/NotificationSubscription" } } } @@ -3033,6 +6362,16 @@ } } }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3054,56 +6393,54 @@ } } } - } - }, - "/api/presentations/{presentationId}/status": { - "get": { + }, + "delete": { "tags": [ - "Presentations" + "Notifications" + ], + "summary": "Delete a notification subscription", + "security": [ + { + "sessionCookie": [] + } ], - "summary": "Get presentation status", - "description": "Returns the current processing status and slide readiness.", "parameters": [ { "in": "path", - "name": "presentationId", + "name": "subscriptionId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the presentation" + } } ], "responses": { "200": { - "description": "Presentation status", + "description": "Subscription deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { - "status": { - "type": "string", - "enum": [ - "not_started", - "pending", - "processing", - "ready", - "failed" - ] - }, - "slidesReady": { - "type": "integer" - }, - "totalSlides": { - "type": "integer" + "success": { + "type": "boolean" } } } } } }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3127,119 +6464,111 @@ } } }, - "/api/qa": { - "post": { + "/api/notifications/events": { + "get": { "tags": [ - "Q&A" + "Notifications" ], - "summary": "Answer a question", - "description": "Answers a viewer question. Checks FAQs first (exact then semantic match at ≥ 0.6 confidence), falls back to GPT-4o with slide context. Returns answer text plus synthesized audio.\n\n**Authentication**: Accepts either a session cookie (admin) or a `presentationId` (public viewer).", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "question", - "slideId" - ], - "properties": { - "question": { - "type": "string", - "minLength": 3 - }, - "slideId": { - "type": "string", - "description": "Current slide ID for context" - }, - "deckId": { - "type": "string", - "format": "uuid" - }, - "presentationId": { - "type": "string", - "format": "uuid", - "description": "Required for unauthenticated viewer access" - }, - "history": { - "type": "array", - "items": { - "type": "object", - "required": [ - "role", - "content" - ], - "properties": { - "role": { - "type": "string", - "enum": [ - "user", - "assistant" - ] - }, - "content": { - "type": "string" - } - } - } - }, - "voiceId": { - "type": "string" - }, - "language": { - "type": "string", - "example": "en", - "description": "BCP-47 language tag" - } - } - } - } + "summary": "List event log", + "description": "Returns a paginated, filterable log of all events for the organization.", + "security": [ + { + "sessionCookie": [] } - }, - "responses": { - "200": { - "description": "Answer with audio", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QaResponse" - } - } + ], + "parameters": [ + { + "in": "query", + "name": "eventType", + "schema": { + "type": "string" + }, + "description": "Filter by event type" + }, + { + "in": "query", + "name": "aggregateType", + "schema": { + "type": "string" } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } + { + "in": "query", + "name": "aggregateId", + "schema": { + "type": "string" } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" } }, - "403": { - "description": "Forbidden", + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Event log", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventLogEntry" + } + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + } + } + } + } } } } }, - "404": { - "description": "Not found", + "401": { + "description": "Unauthorized", "content": { "application/json": { "schema": { @@ -3261,72 +6590,57 @@ } } }, - "/api/narration/single": { + "/api/presentations/{presentationId}/start": { "post": { "tags": [ - "Narration" + "Presentations" ], - "summary": "Synthesize narration for one slide", - "description": "Generates TTS audio for a slide script. Results are cached per session.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "sessionId", - "slideId", - "script" - ], - "properties": { - "sessionId": { - "type": "string", - "description": "Unique session identifier for caching" - }, - "slideId": { - "type": "string" - }, - "script": { - "type": "string", - "minLength": 1 - }, - "voiceId": { - "type": "string" - }, - "presentationId": { - "type": "string", - "format": "uuid", - "description": "Required for unauthenticated viewer access" - } - } - } + "summary": "Start a presentation", + "description": "Triggers lazy generation when a viewer clicks the CTA on the intro page. Safe to call multiple times — duplicate generation requests are ignored.", + "parameters": [ + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } - }, + ], "responses": { "200": { - "description": "Audio URL and word timings", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NarrationResponse" - } - } - } - }, - "400": { - "description": "Bad request", + "description": "Presentation status", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "ready", + "generating" + ] + }, + "phase": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string" + } + } } } } }, - "401": { - "description": "Unauthorized", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -3335,8 +6649,8 @@ } } }, - "403": { - "description": "Forbidden", + "410": { + "description": "Presentation deleted", "content": { "application/json": { "schema": { @@ -3358,73 +6672,36 @@ } } }, - "/api/narration/batch": { - "post": { + "/api/presentations/{presentationId}/product-selection": { + "get": { "tags": [ - "Narration" + "Presentations" ], - "summary": "Synthesize narration for multiple slides", - "description": "Batch TTS generation. Per-slide failures return a fallback audio URL rather than failing the whole batch.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "sessionId", - "slides" - ], - "properties": { - "sessionId": { - "type": "string" - }, - "slides": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "slideId", - "script" - ], - "properties": { - "slideId": { - "type": "string" - }, - "script": { - "type": "string", - "minLength": 1 - }, - "voiceId": { - "type": "string" - } - } - } - } - } - } + "summary": "Get product selections", + "description": "Returns all product tier selections the viewer has made during the presentation.", + "parameters": [ + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } - }, + ], "responses": { "200": { - "description": "Narration responses for each slide", + "description": "Product selections keyed by slide ID", "content": { "application/json": { "schema": { "type": "object", "properties": { - "cachedIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "responses": { - "type": "array", - "items": { - "$ref": "#/components/schemas/NarrationResponse" + "selectedProducts": { + "type": "object", + "additionalProperties": { + "type": "object" } } } @@ -3432,18 +6709,8 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "401": { - "description": "Unauthorized", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -3463,36 +6730,65 @@ } } } - } - }, - "/api/narration/resume": { - "post": { + }, + "put": { "tags": [ - "Narration" + "Presentations" + ], + "summary": "Save a product selection", + "description": "Saves a product tier selection for a specific slide during the presentation.", + "parameters": [ + { + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } ], - "summary": "Synthesize resume narration", - "description": "Generates the spoken bridge from Q&A back into the presentation, using the deck's configured transition prompt.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", + "required": [ + "slideId", + "tier", + "productName", + "price" + ], "properties": { - "deckId": { - "type": "string", - "format": "uuid" + "slideId": { + "type": "string" }, - "presentationId": { + "tier": { "type": "string", - "format": "uuid" + "enum": [ + "good", + "better", + "best" + ] }, - "voiceId": { + "productName": { "type": "string" }, - "language": { - "type": "string", - "example": "en" + "price": { + "type": "string" + }, + "priceSuffix": { + "type": "string" + }, + "productImage": { + "type": "string" + }, + "highlights": { + "type": "array", + "items": { + "type": "string" + } } } } @@ -3501,11 +6797,19 @@ }, "responses": { "200": { - "description": "Resume narration audio", + "description": "Selection saved", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NarrationResponse" + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "selection": { + "type": "object" + } + } } } } @@ -3520,18 +6824,8 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "403": { - "description": "Forbidden", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -3553,39 +6847,76 @@ } } }, - "/api/images": { + "/api/presentations/{presentationId}/sign-contract": { "get": { "tags": [ - "Images" + "Contracts" ], - "summary": "List images", - "description": "Returns all uploaded images for the authenticated organization.", - "security": [ + "summary": "Get contract for signing", + "description": "Returns the contract template, company branding, and product data needed to render the signing view.", + "parameters": [ { - "sessionCookie": [] + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "configId", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Action config to use (uses published config if omitted)" } ], "responses": { "200": { - "description": "List of images", + "description": "Contract data for rendering", "content": { "application/json": { "schema": { "type": "object", "properties": { - "images": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Image" - } + "template": { + "type": "string" + }, + "requirements": { + "type": "object", + "nullable": true + }, + "repName": { + "type": "string", + "nullable": true + }, + "companyName": { + "type": "string" + }, + "companyLogo": { + "type": "string", + "nullable": true + }, + "companyDetails": { + "type": "object" + }, + "branding": { + "type": "object", + "nullable": true + }, + "products": { + "type": "object" } } } } } }, - "401": { - "description": "Unauthorized", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -3605,34 +6936,65 @@ } } } - } - }, - "/api/images/upload": { + }, "post": { "tags": [ - "Images" + "Contracts" ], - "summary": "Upload an image", - "description": "Uploads and optimizes an image (Sharp: max 1920px width, JPEG quality 85). Returns the S3 key and a presigned URL.", - "security": [ + "summary": "Sign a contract", + "description": "Submits a contract signature. Validates the typed signature against the customer name, resolves the template with variables, and creates an audit-trailed signed document.", + "parameters": [ { - "sessionCookie": [] + "in": "path", + "name": "presentationId", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } } ], "requestBody": { "required": true, "content": { - "multipart/form-data": { + "application/json": { "schema": { "type": "object", "required": [ - "file" + "variables", + "signature" ], "properties": { - "file": { + "configId": { "type": "string", - "format": "binary", - "description": "Image file (PNG, JPG, or WebP; max 10 MB)" + "format": "uuid" + }, + "variables": { + "type": "object", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + } + }, + "additionalProperties": { + "type": "string" + } + }, + "signature": { + "type": "string", + "description": "Typed signature (must match the customer name)" + }, + "agreedToTerms": { + "type": "boolean" } } } @@ -3640,15 +7002,26 @@ } }, "responses": { - "201": { - "description": "Uploaded image metadata", + "200": { + "description": "Contract signed", "content": { "application/json": { "schema": { "type": "object", "properties": { - "image": { - "$ref": "#/components/schemas/Image" + "success": { + "type": "boolean" + }, + "documentId": { + "type": "string", + "format": "uuid" + }, + "signedAt": { + "type": "string", + "format": "date-time" + }, + "message": { + "type": "string" } } } @@ -3656,7 +7029,7 @@ } }, "400": { - "description": "Bad request", + "description": "Bad request (signature mismatch or validation error)", "content": { "application/json": { "schema": { @@ -3665,8 +7038,8 @@ } } }, - "401": { - "description": "Unauthorized", + "404": { + "description": "Not found", "content": { "application/json": { "schema": { @@ -3688,32 +7061,73 @@ } } }, - "/api/images/{imageId}": { - "delete": { + "/api/presentations/{presentationId}/events": { + "post": { "tags": [ - "Images" - ], - "summary": "Delete an image", - "security": [ - { - "sessionCookie": [] - } + "Presentations" ], + "summary": "Record a presentation event", + "description": "Ingests interaction events from the presentation viewer (slide views, CTA clicks, Q&A logs). Authenticated via an HMAC token in the request body.", "parameters": [ { "in": "path", - "name": "imageId", + "name": "presentationId", "required": true, "schema": { "type": "string", "format": "uuid" - }, - "description": "UUID of the image" + } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "_token", + "type" + ], + "properties": { + "_token": { + "type": "string", + "description": "HMAC authentication token" + }, + "type": { + "type": "string", + "enum": [ + "presentation_opened", + "slide_viewed", + "cta_clicked", + "action_detection", + "action_execution" + ], + "description": "Event type" + }, + "slideId": { + "type": "string" + }, + "slideIndex": { + "type": "integer" + }, + "durationMs": { + "type": "integer" + }, + "ctaType": { + "type": "string" + }, + "ctaLabel": { + "type": "string" + } + } + } + } + } + }, "responses": { "200": { - "description": "Image deleted", + "description": "Event recorded", "content": { "application/json": { "schema": { @@ -3727,8 +7141,18 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "401": { - "description": "Unauthorized", + "description": "Invalid token", "content": { "application/json": { "schema": { @@ -3737,8 +7161,8 @@ } } }, - "404": { - "description": "Not found", + "429": { + "description": "Rate limit exceeded", "content": { "application/json": { "schema": { @@ -3760,37 +7184,104 @@ } } }, - "/api/voices/list": { - "get": { + "/api/address/validate": { + "post": { "tags": [ - "Voices" + "Utility" ], - "summary": "List available voices", - "description": "Returns a curated list of ElevenLabs voices suitable for sales presentations.", + "summary": "Validate an address", + "description": "Validates a physical address using geocoding. Returns formatted address, coordinates, and normalized components.", "security": [ { "sessionCookie": [] } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "address", + "city", + "country" + ], + "properties": { + "address": { + "type": "string", + "example": "123 Main St" + }, + "city": { + "type": "string", + "example": "Phoenix" + }, + "state": { + "type": "string", + "example": "AZ" + }, + "zip": { + "type": "string", + "example": "85001" + }, + "country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "example": "US", + "description": "ISO 3166-1 alpha-2 country code" + } + } + } + } + } + }, "responses": { "200": { - "description": "Voice list with preview URLs", + "description": "Validation result", "content": { "application/json": { "schema": { "type": "object", "properties": { - "voices": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Voice" + "valid": { + "type": "boolean" + }, + "formattedAddress": { + "type": "string" + }, + "coordinates": { + "type": "object", + "properties": { + "lat": { + "type": "number" + }, + "lng": { + "type": "number" + } } + }, + "normalized": { + "type": "object" + }, + "error": { + "type": "string" } } } } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, "401": { "description": "Unauthorized", "content": { @@ -3810,35 +7301,58 @@ } } } + }, + "503": { + "description": "Validation service not configured", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, - "/api/health": { + "/api/fonts": { "get": { "tags": [ - "Utility" + "Fonts" ], - "summary": "Health check", - "description": "Returns `{ ok: true }` if the service is running.", + "summary": "List available fonts", + "description": "Returns a list of available Google Fonts for use in deck branding. Results are cached for 24 hours.", "responses": { "200": { - "description": "Service is healthy", + "description": "Font list", "content": { "application/json": { "schema": { "type": "object", "properties": { - "ok": { - "type": "boolean" + "fonts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Font" + } } } } } } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } } } -} \ No newline at end of file +} diff --git a/sales-copilot/quickstart.mdx b/sales-copilot/quickstart.mdx index 9e8e420..81c5213 100644 --- a/sales-copilot/quickstart.mdx +++ b/sales-copilot/quickstart.mdx @@ -59,6 +59,10 @@ Open the **Presentations** tab and click **New Presentation**. Enter the homeown You'll get a shareable link. Send it to the homeowner however you like — text, email, CRM, whatever fits your workflow. When they open it, they'll see a branded landing page with their name and address already in the narration. + +If you're integrating with a CRM or automation platform, you can also create presentations programmatically via the API using a Bearer token. Call `POST /api/decks/{deckId}/presentations` with an `Authorization: Bearer ` header instead of a session cookie. See the [API reference](/sales-copilot/api-reference) for details. + + ## What's next - [Setting Up Your First Deck](/sales-copilot/guides/deck-types) — deeper dive on deck creation and slide editing