From 1b5eebdb78016ee9fe8678ecedcff48ff64c9e38 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:02:15 +0000 Subject: [PATCH] Add comprehensive API reference for all Sales CoPilot endpoints Generated-By: mintlify-agent --- introduction.mdx | 39 +- openapi.json | 3053 +++++++++++++++++++++++++++++++++++++++++++++- quickstart.mdx | 140 ++- 3 files changed, 3212 insertions(+), 20 deletions(-) diff --git a/introduction.mdx b/introduction.mdx index c07e6a2..c313453 100644 --- a/introduction.mdx +++ b/introduction.mdx @@ -1,22 +1,39 @@ --- title: Introduction -description: "Welcome to the documentation" +description: Sales CoPilot API documentation for building and managing AI-powered sales presentations. --- -## Overview +Sales CoPilot by Demand IQ lets you create AI-narrated, personalized sales presentations that engage homeowners with dynamic content, voice-powered Q&A, and built-in contract signing. -Add a short description of what you're documenting here. This is the first page users will land on. +## Core concepts -## Getting Started +- **Decks** are reusable presentation templates containing slides, branding, FAQs, and Q&A settings. +- **Slides** hold the content for each page of a presentation — titles, bullet points, narration scripts, and optional product pricing tiers. +- **Presentations** are personalized instances of a deck created for a specific homeowner. Each presentation gets a unique shareable URL. +- **Narration** is generated using text-to-speech. Narration scripts support personalization tokens like `{{first_name}}` that are replaced at presentation time. +- **Q&A** uses AI to answer homeowner questions during the presentation, drawing from your FAQs and knowledge base. -Get up and running quickly with the guides below. +## What you can do with the API - - Get started in minutes + + Build presentation templates with slides, branding, and FAQs + + + Create personalized presentations for homeowners with dynamic pricing + + + Upload images, import contracts, and configure knowledge base documents + + + Subscribe to events like slide views, Q&A interactions, and contract signings + +## Authentication + +All API endpoints (except the public presentation viewer) require session-based authentication. Call `POST /api/auth/login` with your credentials to establish a session, then include the session cookie on subsequent requests. + +## API reference + +Browse the full API reference in the sidebar to see every available endpoint, organized by domain: decks, slides, presentations, narration, Q&A, notifications, and more. diff --git a/openapi.json b/openapi.json index 9122fe7..aa93470 100644 --- a/openapi.json +++ b/openapi.json @@ -3,18 +3,3067 @@ "info": { "title": "Sales CoPilot API", "version": "1.0.0", - "description": "API reference for Sales CoPilot by Demand IQ" + "description": "API reference for Sales CoPilot by Demand IQ. Build and manage AI-powered sales presentations, generate personalized narration, and track homeowner engagement." }, + "servers": [ + { + "url": "https://app.demandiq.com", + "description": "Production" + } + ], + "tags": [ + { "name": "Auth", "description": "Session authentication" }, + { "name": "Decks", "description": "Manage presentation decks" }, + { "name": "Slides", "description": "Manage slides within decks" }, + { "name": "Presentations", "description": "Create and manage personalized presentations" }, + { "name": "Public presentations", "description": "Public viewer endpoints for presentations" }, + { "name": "Narration", "description": "Text-to-speech narration generation" }, + { "name": "Q&A", "description": "AI-powered question answering" }, + { "name": "Actions", "description": "Voice/text action classification" }, + { "name": "FAQs", "description": "Manage frequently asked questions" }, + { "name": "Branding", "description": "Deck branding and theming" }, + { "name": "Images", "description": "Image upload and management" }, + { "name": "Notifications", "description": "Event subscriptions and delivery" }, + { "name": "Company", "description": "Organization settings and knowledge base" }, + { "name": "Contracts", "description": "Contract import and signing" }, + { "name": "Config", "description": "Client configuration" }, + { "name": "Address", "description": "Address validation" }, + { "name": "Roof measurements", "description": "Roof measurement requests" }, + { "name": "Voices", "description": "Available TTS voices" }, + { "name": "Fonts", "description": "Available fonts" }, + { "name": "Health", "description": "Health checks" } + ], "paths": { "/api/health": { "get": { "summary": "Health check", "description": "Returns the health status of the API.", "operationId": "getHealth", + "tags": ["Health"], + "responses": { + "200": { + "description": "API is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "string", "example": "healthy" }, + "timestamp": { "type": "string", "format": "date-time" } + } + } + } + } + } + } + } + }, + "/api/auth/login": { + "post": { + "summary": "Log in", + "description": "Authenticate with email and password. Returns user and organization info and sets a session cookie.", + "operationId": "login", + "tags": ["Auth"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "password"], + "properties": { + "email": { "type": "string", "format": "email", "example": "sales@acmeroofing.com" }, + "password": { "type": "string", "example": "s3cureP@ss" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Login successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthResponse" + } + } + } + }, + "400": { "description": "Missing required fields" }, + "401": { "description": "Invalid credentials" }, + "429": { "description": "Too many login attempts" } + } + } + }, + "/api/auth/logout": { + "post": { + "summary": "Log out", + "description": "Revoke the current session and clear the session cookie.", + "operationId": "logout", + "tags": ["Auth"], + "responses": { + "200": { + "description": "Logged out successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean", "example": true } + } + } + } + } + } + } + } + }, + "/api/auth/me": { + "get": { + "summary": "Get current user", + "description": "Return the authenticated user's profile and organization info.", + "operationId": "getMe", + "tags": ["Auth"], + "responses": { + "200": { + "description": "Current session info", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthResponse" + } + } + } + }, + "401": { "description": "Not authenticated" } + } + } + }, + "/api/decks": { + "get": { + "summary": "List decks", + "description": "List all decks for your organization, ordered by creation date (newest first).", + "operationId": "listDecks", + "tags": ["Decks"], "responses": { "200": { - "description": "API is healthy" + "description": "List of decks", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "decks": { + "type": "array", + "items": { "$ref": "#/components/schemas/DeckSummary" } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Create a deck", + "description": "Create a new presentation deck with a name, type, and optional configuration.", + "operationId": "createDeck", + "tags": ["Decks"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string", "example": "Solar Roof Pitch" }, + "description": { "type": "string", "example": "Standard residential solar sales deck" }, + "type": { "type": "string", "enum": ["structured", "image-based"], "default": "structured" }, + "deckContext": { "type": "string", "description": "Background info for AI narration generation" }, + "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": "Invalid input" } + } + } + }, + "/api/decks/{deckId}": { + "get": { + "summary": "Get a deck", + "description": "Retrieve a deck with all its content including slides, FAQs, branding, and Q&A settings.", + "operationId": "getDeck", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "Deck details", + "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" }, + "qaSettings": { "$ref": "#/components/schemas/QASettings" }, + "presentationsCount": { "type": "integer", "example": 12 } + } + } + } + } + }, + "404": { "description": "Deck not found" } + } + }, + "put": { + "summary": "Update a deck", + "description": "Update a deck's metadata fields such as name, description, type, and pricing configuration.", + "operationId": "updateDeck", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "type": { "type": "string", "enum": ["structured", "image-based"] }, + "isActive": { "type": "boolean" }, + "deckContext": { "type": "string" }, + "action": { "type": "object", "description": "Action configuration" }, + "contact": { "type": "object", "description": "Contact info" }, + "priceVariables": { + "type": "array", + "items": { "$ref": "#/components/schemas/PriceVariable" } + }, + "priceFormatting": { "$ref": "#/components/schemas/PriceFormatting" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Deck updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deck": { "$ref": "#/components/schemas/Deck" } + } + } + } + } + }, + "404": { "description": "Deck not found" } + } + }, + "delete": { + "summary": "Delete a deck", + "description": "Delete a deck and all associated data (slides, FAQs, branding).", + "operationId": "deleteDeck", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "Deck deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean", "example": true } + } + } + } + } + }, + "404": { "description": "Deck not found" } + } + } + }, + "/api/decks/{deckId}/slides": { + "get": { + "summary": "List slides", + "description": "List all slides for a deck, ordered by position. Includes product data for product pricing slides.", + "operationId": "listSlides", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "List of slides", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slides": { "type": "array", "items": { "$ref": "#/components/schemas/Slide" } } + } + } + } + } + } + } + }, + "post": { + "summary": "Create a slide", + "description": "Add a new slide to a deck. Provide a title, narration script, and optional bullet points and hero image.", + "operationId": "createSlide", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["slideId", "title", "narrationScript"], + "properties": { + "slideId": { "type": "string", "example": "intro-slide" }, + "title": { "type": "string", "example": "Welcome to Your Solar Journey" }, + "narrationScript": { "type": "string", "example": "Hi {{first_name}}, let's walk through your personalized solar proposal." }, + "bullets": { "type": "array", "items": { "type": "string" }, "example": ["30% energy savings", "25-year warranty"] }, + "slideContext": { "type": "string" }, + "audioUrl": { "type": "string" }, + "heroImg": { "type": "string" }, + "orderIndex": { "type": "integer" }, + "slideType": { "type": "string", "enum": ["standard", "product_pricing_v1"], "default": "standard" }, + "productData": { "$ref": "#/components/schemas/ProductSlideData" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Slide created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slide": { "$ref": "#/components/schemas/Slide" } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/slides/reorder": { + "put": { + "summary": "Reorder slides", + "description": "Update the position of slides in a deck by providing an array of slide ID and order index pairs.", + "operationId": "reorderSlides", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["slides"], + "properties": { + "slides": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "orderIndex"], + "properties": { + "id": { "type": "string", "format": "uuid" }, + "orderIndex": { "type": "integer" } + } + } + } + } + } + } + } + }, + "responses": { + "200": { "description": "Slides reordered" } + } + } + }, + "/api/slides/{slideId}": { + "put": { + "summary": "Update a slide", + "description": "Update a slide's content including title, bullets, narration script, audio, and hero image.", + "operationId": "updateSlide", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/SlideId" } + ], + "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" }, + "audioUrl": { "type": "string" }, + "heroImg": { "type": "string" }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } } + } + } + } + } + }, + "responses": { + "200": { + "description": "Slide updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slide": { "$ref": "#/components/schemas/Slide" } + } + } + } + } + }, + "404": { "description": "Slide not found" } + } + }, + "delete": { + "summary": "Delete a slide", + "description": "Remove a slide from its deck.", + "operationId": "deleteSlide", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/SlideId" } + ], + "responses": { + "200": { "description": "Slide deleted" }, + "404": { "description": "Slide not found" } + } + } + }, + "/api/slides/{slideId}/product-data": { + "get": { + "summary": "Get product data", + "description": "Retrieve product pricing tiers (good/better/best) for a slide.", + "operationId": "getProductData", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/SlideId" } + ], + "responses": { + "200": { + "description": "Product data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "productData": { "$ref": "#/components/schemas/ProductSlideData" } + } + } + } + } + } + } + }, + "put": { + "summary": "Update product data", + "description": "Create or update product pricing tiers for a slide. Automatically sets the slide type to `product_pricing_v1`.", + "operationId": "updateProductData", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/SlideId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductSlideData" + } + } + } + }, + "responses": { + "200": { "description": "Product data updated" } + } + }, + "delete": { + "summary": "Remove product data", + "description": "Remove product data from a slide, converting it back to a standard slide.", + "operationId": "deleteProductData", + "tags": ["Slides"], + "parameters": [ + { "$ref": "#/components/parameters/SlideId" } + ], + "responses": { + "200": { "description": "Product data removed" } + } + } + }, + "/api/slides/analyze-and-generate": { + "post": { + "summary": "Analyze slide image", + "description": "Upload a slide image for AI analysis. Extracts title and key points using GPT-4o Vision, then generates a narration script.", + "operationId": "analyzeAndGenerateSlide", + "tags": ["Slides"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["imageUrl"], + "properties": { + "imageUrl": { "type": "string", "description": "URL of the slide image to analyze" }, + "deckContext": { "type": "string" }, + "isIntro": { "type": "boolean" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Analysis results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "bullets": { "type": "array", "items": { "type": "string" } }, + "narrationScript": { "type": "string" } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/presentations": { + "get": { + "summary": "List presentations", + "description": "List all presentations for a deck, ordered by creation date (newest first).", + "operationId": "listPresentations", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "List of presentations", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "presentations": { + "type": "array", + "items": { "$ref": "#/components/schemas/PresentationSummary" } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Create a presentation", + "description": "Create a personalized presentation for a homeowner. Validates the address, evaluates pricing formulas, snapshots slides, and begins audio generation. Returns a shareable presentation URL.", + "operationId": "createPresentation", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["homeowner"], + "properties": { + "homeowner": { "$ref": "#/components/schemas/HomeownerData" }, + "voice_settings": { "$ref": "#/components/schemas/VoiceSettings" }, + "language_settings": { "$ref": "#/components/schemas/LanguageSettings" }, + "price_inputs": { + "type": "object", + "additionalProperties": { "type": "number" }, + "description": "Variable values for pricing formula evaluation", + "example": { "roof_squares": 32, "system_size_kw": 8.5 } + }, + "measurement_strategy": { + "type": "string", + "enum": ["auto", "immediate", "manual"], + "default": "manual" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Presentation created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deck_presentation_id": { "type": "string", "format": "uuid" }, + "url": { "type": "string", "example": "https://app.demandiq.com/p/abc123" }, + "status": { "type": "string", "enum": ["not_started", "pending"] } + } + } + } + } + }, + "400": { "description": "Invalid input or deck has no slides" }, + "422": { "description": "Address validation failed" } + } + } + }, + "/api/presentations/{presentationId}": { + "patch": { + "summary": "Update a presentation", + "description": "Update editable fields on a presentation such as homeowner data, voice settings, and price inputs.", + "operationId": "updatePresentation", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "homeownerData": { "$ref": "#/components/schemas/HomeownerData" }, + "voiceSettings": { "$ref": "#/components/schemas/VoiceSettings" }, + "languageSettings": { "$ref": "#/components/schemas/LanguageSettings" }, + "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" } + } + } + } + } + }, + "404": { "description": "Presentation not found" } + } + }, + "delete": { + "summary": "Delete a presentation", + "description": "Soft-delete a presentation. The presentation URL will no longer be accessible.", + "operationId": "deletePresentation", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "responses": { + "200": { + "description": "Presentation deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { "type": "string" }, + "presentationId": { "type": "string", "format": "uuid" } + } + } + } + } + }, + "404": { "description": "Presentation not found" } + } + } + }, + "/api/presentations/{presentationId}/status": { + "get": { + "summary": "Get presentation status", + "description": "Poll the generation status of a presentation. Use this to track progress during audio generation.", + "operationId": "getPresentationStatus", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "responses": { + "200": { + "description": "Presentation status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "status": { "type": "string", "enum": ["not_started", "pending", "generating", "ready", "failed"] }, + "phase": { "type": "string", "enum": ["measuring", "analyzing", "generating", "preparing"] }, + "deck_id": { "type": "string", "format": "uuid" }, + "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { "type": "string", "format": "date-time" }, + "generation_started_at": { "type": "string", "format": "date-time" }, + "measurement_status": { "type": "string" }, + "measurement_request_id": { "type": "string" } + } + } + } + } + }, + "404": { "description": "Presentation not found" }, + "410": { "description": "Presentation was deleted" } + } + } + }, + "/api/presentations/{presentationId}/start": { + "post": { + "summary": "Start presentation generation", + "description": "Trigger lazy generation for a presentation. Call this when the homeowner clicks the CTA on the intro page. Safe to call multiple times — duplicate requests are ignored.", + "operationId": "startPresentation", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "responses": { + "200": { + "description": "Generation started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "string" }, + "message": { "type": "string" } + } + } + } + } + } + } + } + }, + "/api/presentations/{presentationId}/sign-contract": { + "get": { + "summary": "Get contract template", + "description": "Retrieve the contract template for preview. Returns the template markdown, requirements, rep name, company info, and product selections.", + "operationId": "getContract", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "responses": { + "200": { + "description": "Contract template data" + }, + "404": { "description": "Presentation or contract template not found" } + } + }, + "post": { + "summary": "Sign a contract", + "description": "Sign a contract for a presentation. Validates the signature against the customer name, resolves template variables, and creates a signed document record.", + "operationId": "signContract", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["signature", "customerName"], + "properties": { + "signature": { "type": "string", "description": "Customer's typed signature" }, + "customerName": { "type": "string", "description": "Customer's full name (must match signature)" } + } + } + } } + }, + "responses": { + "200": { "description": "Contract signed" }, + "400": { "description": "Signature does not match customer name" } + } + } + }, + "/api/presentations/{presentationId}/product-selection": { + "get": { + "summary": "Get product selections", + "description": "Retrieve all product tier selections made by a homeowner for a presentation.", + "operationId": "getProductSelections", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "responses": { + "200": { "description": "Product selections" } + } + }, + "put": { + "summary": "Save product selection", + "description": "Save a homeowner's product tier selection for a specific slide.", + "operationId": "saveProductSelection", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["slideId", "selectedTier"], + "properties": { + "slideId": { "type": "string" }, + "selectedTier": { "type": "string", "enum": ["good", "better", "best"] } + } + } + } + } + }, + "responses": { + "200": { "description": "Selection saved" } + } + } + }, + "/api/presentations/{presentationId}/events": { + "post": { + "summary": "Ingest presentation event", + "description": "Submit a presentation event for tracking. Supports multiple event types: Q&A interactions, slide views, CTA clicks, and presentation opens. Requires HMAC authentication via the `_token` field.", + "operationId": "ingestEvent", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["_token"], + "properties": { + "_token": { "type": "string", "description": "HMAC authentication token" }, + "type": { + "type": "string", + "enum": ["slide_viewed", "cta_clicked", "presentation_opened", "action_detection", "action_execution"], + "description": "Event type. Omit for Q&A log events." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Event recorded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean" }, + "error": { "type": "string", "nullable": true } + } + } + } + } + }, + "401": { "description": "Invalid or missing token" }, + "429": { "description": "Rate limited" } + } + } + }, + "/api/presentations/{presentationId}/events-token": { + "post": { + "summary": "Refresh event token", + "description": "Exchange a recently expired event ingest token for a new 24-hour token. The expired token must be within 7 days of expiration.", + "operationId": "refreshEventToken", + "tags": ["Presentations"], + "parameters": [ + { "$ref": "#/components/parameters/PresentationId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["expiredToken"], + "properties": { + "expiredToken": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "New token issued", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { "type": "string" } + } + } + } + } + }, + "429": { "description": "Rate limited" } + } + } + }, + "/api/deck_presentations/{deckPresentationId}": { + "get": { + "summary": "Load presentation for viewer", + "description": "Load complete presentation data for the public viewer. Returns slides with narration audio, branding, FAQs, and Q&A settings.", + "operationId": "loadPresentation", + "tags": ["Public presentations"], + "parameters": [ + { + "name": "deckPresentationId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { + "description": "Full presentation payload for the viewer" + }, + "404": { "description": "Presentation not found or deleted" }, + "410": { "description": "Parent deck was deleted" } + } + } + }, + "/api/narration": { + "post": { + "summary": "Generate narration", + "description": "Synthesize speech for a slide's narration script using the configured TTS provider.", + "operationId": "generateNarration", + "tags": ["Narration"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["script"], + "properties": { + "script": { "type": "string", "example": "Welcome to your personalized solar proposal, Sarah." }, + "voiceId": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Audio generated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "audioUrl": { "type": "string" }, + "source": { "type": "string", "enum": ["elevenlabs", "openai"] } + } + } + } + } + } + } + } + }, + "/api/narration/single": { + "post": { + "summary": "Generate single slide narration", + "description": "Synthesize speech for a single slide with session-based caching. Returns cached results for previously generated slides.", + "operationId": "generateSingleNarration", + "tags": ["Narration"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sessionId", "slideId", "script"], + "properties": { + "sessionId": { "type": "string" }, + "slideId": { "type": "string" }, + "script": { "type": "string" }, + "voiceId": { "type": "string" }, + "presentationId": { "type": "string", "description": "Required for public access (unauthenticated)" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Narration result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slideId": { "type": "string" }, + "script": { "type": "string" }, + "audioUrl": { "type": "string" }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } }, + "source": { "type": "string", "enum": ["elevenlabs", "openai", "fallback"] } + } + } + } + } + } + } + } + }, + "/api/narration/batch": { + "post": { + "summary": "Batch generate narration", + "description": "Generate narration for multiple slides at once. Uses session-based caching and processes uncached slides sequentially.", + "operationId": "batchNarration", + "tags": ["Narration"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["sessionId", "slides"], + "properties": { + "sessionId": { "type": "string" }, + "slides": { + "type": "array", + "items": { + "type": "object", + "required": ["slideId", "script"], + "properties": { + "slideId": { "type": "string" }, + "script": { "type": "string" } + } + } + }, + "voiceId": { "type": "string" }, + "presentationId": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Batch narration results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "slideId": { "type": "string" }, + "audioUrl": { "type": "string" }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } }, + "source": { "type": "string" } + } + } + } + } + } + } + } + } + } + } + }, + "/api/narration/resume": { + "post": { + "summary": "Generate resume narration", + "description": "Generate a bridge narration for resuming the presentation after a Q&A interaction.", + "operationId": "resumeNarration", + "tags": ["Narration"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slideContext": { "type": "string" }, + "qaHistory": { "type": "array", "items": { "type": "object" } }, + "voiceId": { "type": "string" }, + "language": { "type": "string" }, + "presentationId": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Resume narration audio", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "audioUrl": { "type": "string" }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } } + } + } + } + } + } + } + } + }, + "/api/narration/qa-audio": { + "post": { + "summary": "Generate Q&A prompt audio", + "description": "Generate and store audio for Q&A resume or transition prompts. Replaces any existing audio.", + "operationId": "generateQAAudio", + "tags": ["Narration"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["deckId", "promptType", "text"], + "properties": { + "deckId": { "type": "string", "format": "uuid" }, + "promptType": { "type": "string", "enum": ["resume", "transition"] }, + "text": { "type": "string" }, + "voiceId": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { "description": "Audio generated and stored" } + } + } + }, + "/api/qa": { + "post": { + "summary": "Ask a question", + "description": "Submit a question during a presentation. Routes through FAQ matching (exact, then semantic) and falls back to AI generation. Supports both authenticated admin and public access.", + "operationId": "askQuestion", + "tags": ["Q&A"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["question", "slideId"], + "properties": { + "question": { "type": "string", "minLength": 3, "example": "How long does solar panel installation take?" }, + "slideId": { "type": "string" }, + "history": { + "type": "array", + "items": { + "type": "object", + "properties": { + "role": { "type": "string", "enum": ["user", "assistant"] }, + "content": { "type": "string" } + } + } + }, + "voiceId": { "type": "string" }, + "deckId": { "type": "string", "description": "Required for authenticated admin access" }, + "presentationId": { "type": "string", "description": "Required for public access" }, + "language": { "type": "string", "example": "en" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Answer", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "answer": { "type": "string" }, + "audioUrl": { "type": "string" }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } }, + "source": { "type": "string", "enum": ["faq", "cached", "openai", "fallback"] }, + "relatedSlideId": { "type": "string" } + } + } + } + } + }, + "400": { "description": "Invalid question or missing required fields" } + } + } + }, + "/api/intent/continue": { + "post": { + "summary": "Classify user intent", + "description": "Classify a user's response after Q&A to determine whether they want to continue the presentation, go back, or ask another question.", + "operationId": "classifyIntent", + "tags": ["Actions"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["input"], + "properties": { + "input": { "type": "string", "example": "Yes, let's keep going" }, + "presentationId": { "type": "string" }, + "didNavigateToNewSlide": { "type": "boolean" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Intent classification", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "intent": { "type": "string", "enum": ["continue", "go_back", "question"] } + } + } + } + } + } + } + } + }, + "/api/actions": { + "post": { + "summary": "Classify voice/text action", + "description": "Classify user voice or text input as a presentation control command (navigation, audio, CTA) or route to Q&A. Uses AI classification with regex fallback.", + "operationId": "classifyAction", + "tags": ["Actions"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["input"], + "properties": { + "input": { "type": "string", "maxLength": 500, "example": "Go to slide 3" }, + "presentationId": { "type": "string" }, + "context": { + "type": "object", + "properties": { + "currentSlide": { "type": "integer", "minimum": 1 }, + "totalSlides": { "type": "integer", "minimum": 1 }, + "isPlaying": { "type": "boolean" }, + "volume": { "type": "number", "minimum": 0, "maximum": 100 }, + "awaitingResume": { "type": "boolean" }, + "didNavigateToNewSlide": { "type": "boolean" } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Classification result", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { "type": "string", "const": "action" }, + "action": { + "type": "object", + "properties": { + "category": { "type": "string", "enum": ["navigation", "audio", "cta", "continuation"] }, + "action": { "type": "string" } + } + }, + "confidence": { "type": "number" } + } + }, + { + "type": "object", + "properties": { + "type": { "type": "string", "const": "no_action" } + } + } + ] + } + } + } + } + } + } + }, + "/api/decks/{deckId}/faqs": { + "get": { + "summary": "List FAQs", + "description": "List all FAQs for a deck.", + "operationId": "listFAQs", + "tags": ["FAQs"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "List of FAQs", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "faqs": { "type": "array", "items": { "$ref": "#/components/schemas/FAQ" } } + } + } + } + } + } + } + }, + "post": { + "summary": "Create an FAQ", + "description": "Add an FAQ to a deck. Automatically generates TTS audio for the answer.", + "operationId": "createFAQ", + "tags": ["FAQs"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["userQuestion", "cannedAnswer"], + "properties": { + "userQuestion": { "type": "string", "example": "How long does installation take?" }, + "cannedAnswer": { "type": "string", "example": "A typical installation takes one to two business days." }, + "relatedSlideId": { "type": "string" } + } + } + } + } + }, + "responses": { + "201": { + "description": "FAQ created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "faq": { "$ref": "#/components/schemas/FAQ" }, + "warning": { "type": "string", "description": "Set if audio generation failed" } + } + } + } + } + } + } + } + }, + "/api/faqs/{faqId}": { + "put": { + "summary": "Update an FAQ", + "description": "Update an FAQ's question, answer, or related slide. Regenerates TTS audio when the answer text changes.", + "operationId": "updateFAQ", + "tags": ["FAQs"], + "parameters": [ + { + "name": "faqId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userQuestion": { "type": "string" }, + "cannedAnswer": { "type": "string" }, + "relatedSlideId": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "FAQ updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "faq": { "$ref": "#/components/schemas/FAQ" } + } + } + } + } + }, + "404": { "description": "FAQ not found" } + } + }, + "delete": { + "summary": "Delete an FAQ", + "description": "Remove an FAQ from its deck.", + "operationId": "deleteFAQ", + "tags": ["FAQs"], + "parameters": [ + { + "name": "faqId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "FAQ deleted" }, + "404": { "description": "FAQ not found" } + } + } + }, + "/api/decks/{deckId}/branding": { + "get": { + "summary": "Get branding", + "description": "Retrieve the branding configuration for a deck including colors, logo, fonts, and CTA.", + "operationId": "getBranding", + "tags": ["Branding"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "Branding configuration", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branding": { "$ref": "#/components/schemas/Branding" } + } + } + } + } + } + } + }, + "put": { + "summary": "Update branding", + "description": "Create or update branding for a deck. Requires at minimum a color palette and CTA configuration.", + "operationId": "updateBranding", + "tags": ["Branding"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["palette", "cta"], + "properties": { + "name": { "type": "string", "example": "Acme Roofing" }, + "logo": { "type": "string", "description": "Image URL or S3 key for the company logo" }, + "avatar": { "type": "string", "description": "Image URL or S3 key for the chat avatar" }, + "chatName": { "type": "string", "example": "Acme Assistant" }, + "chatGreeting": { "type": "string", "example": "Hi! Ask me anything about your proposal." }, + "palette": { + "type": "object", + "required": ["primary", "primaryLight", "accent", "neutral", "text"], + "properties": { + "primary": { "type": "string", "example": "#1a73e8" }, + "primaryLight": { "type": "string", "example": "#4a90d9" }, + "accent": { "type": "string", "example": "#ff6b35" }, + "neutral": { "type": "string", "example": "#f5f5f5" }, + "text": { "type": "string", "example": "#333333" } + } + }, + "fonts": { "type": "object", "description": "Font pairing configuration" }, + "cta": { + "type": "object", + "required": ["headline", "body", "buttonText"], + "properties": { + "headline": { "type": "string", "example": "Ready to go solar?" }, + "body": { "type": "string", "example": "Schedule your free consultation today." }, + "buttonText": { "type": "string", "example": "Get started" } + } + }, + "contact": { "type": "object" }, + "loi": { "type": "object" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Branding updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "branding": { "$ref": "#/components/schemas/Branding" } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/qa-settings": { + "get": { + "summary": "Get Q&A settings", + "description": "Retrieve the Q&A settings for a deck, including resume and transition prompt text and audio.", + "operationId": "getQASettings", + "tags": ["Q&A"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "Q&A settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "qaSettings": { "$ref": "#/components/schemas/QASettings" } + } + } + } + } + } + } + }, + "put": { + "summary": "Update Q&A settings", + "description": "Update Q&A resume and transition prompt text. Changing the text clears any previously generated audio so it can be regenerated.", + "operationId": "updateQASettings", + "tags": ["Q&A"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "resumePromptText": { "type": "string", "example": "Great question! Now, let's get back to your proposal." }, + "transitionPromptText": { "type": "string", "example": "Moving on to the next section of your presentation." } + } + } + } + } + }, + "responses": { + "200": { + "description": "Q&A settings updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "qaSettings": { "$ref": "#/components/schemas/QASettings" } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/intro": { + "get": { + "summary": "Get intro page", + "description": "Retrieve the intro page configuration for a deck.", + "operationId": "getIntro", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { "description": "Intro page configuration" } + } + }, + "put": { + "summary": "Update intro page", + "description": "Create or update the intro page for a deck. Enabling an intro page automatically sets the deck to lazy generation mode.", + "operationId": "updateIntro", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "backgroundImage": { "type": "string" }, + "headline": { "type": "string", "example": "Your Custom Solar Proposal" }, + "subtitle": { "type": "string" }, + "ctaButtonLabel": { "type": "string", "example": "View My Proposal" } + } + } + } + } + }, + "responses": { + "200": { "description": "Intro page updated" } + } + }, + "delete": { + "summary": "Delete intro page", + "description": "Remove the intro page configuration from a deck.", + "operationId": "deleteIntro", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { "description": "Intro page deleted" } + } + } + }, + "/api/decks/{deckId}/generate": { + "post": { + "summary": "Generate slides from images", + "description": "Start async generation of slides from uploaded images for image-based decks. Uses AI vision analysis to extract content and generate narration. Returns a job ID for tracking progress.", + "operationId": "generateSlides", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { "type": "string", "format": "binary" } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Generation job started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobId": { "type": "string", "format": "uuid" } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/generate/status": { + "get": { + "summary": "Get generation status", + "description": "Check the status of a slide generation job. Reports progress, current slide count, and any errors.", + "operationId": "getGenerationStatus", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" }, + { + "name": "jobId", + "in": "query", + "schema": { "type": "string", "format": "uuid" }, + "description": "Specific job ID to check. Defaults to the latest job." + } + ], + "responses": { + "200": { + "description": "Generation job status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "string" }, + "progress": { "type": "integer" }, + "currentSlide": { "type": "integer" }, + "totalSlides": { "type": "integer" }, + "errors": { "type": "array", "items": { "type": "string" } } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/generate-preview": { + "post": { + "summary": "Generate preview audio", + "description": "Generate preview audio for all slides that have personalization tokens but no preview audio yet.", + "operationId": "generatePreview", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { "description": "Preview audio generation started" } + } + } + }, + "/api/decks/{deckId}/preview-status": { + "get": { + "summary": "Get preview status", + "description": "Check how many slides need preview audio generation.", + "operationId": "getPreviewStatus", + "tags": ["Decks"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { + "description": "Preview status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "needsPreview": { "type": "boolean" }, + "slidesNeedingPreview": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/decks/{deckId}/action-configs": { + "get": { + "summary": "List action configs", + "description": "List all action configurations (contract and LOI templates) for a deck.", + "operationId": "listActionConfigs", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "responses": { + "200": { "description": "List of action configs" } + } + }, + "post": { + "summary": "Create an action config", + "description": "Create a new contract or LOI template for a deck.", + "operationId": "createActionConfig", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "templateMarkdown": { "type": "string" }, + "disclosures": { "type": "string" }, + "requirements": { "type": "array", "items": { "type": "string" } }, + "repName": { "type": "string" }, + "status": { "type": "string", "enum": ["draft", "published"] }, + "type": { "type": "string", "enum": ["contract", "loi"] } + } + } + } + } + }, + "responses": { + "201": { "description": "Action config created" } + } + } + }, + "/api/decks/{deckId}/action-configs/{configId}": { + "get": { + "summary": "Get an action config", + "description": "Retrieve a specific action config by ID.", + "operationId": "getActionConfig", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" }, + { + "name": "configId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "Action config details" }, + "404": { "description": "Not found" } + } + }, + "put": { + "summary": "Update an action config", + "description": "Update a contract or LOI template.", + "operationId": "updateActionConfig", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" }, + { + "name": "configId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "templateMarkdown": { "type": "string" }, + "disclosures": { "type": "string" }, + "requirements": { "type": "array", "items": { "type": "string" } }, + "repName": { "type": "string" }, + "status": { "type": "string", "enum": ["draft", "published"] } + } + } + } + } + }, + "responses": { + "200": { "description": "Action config updated" } + } + }, + "delete": { + "summary": "Delete an action config", + "description": "Remove a contract or LOI template from a deck.", + "operationId": "deleteActionConfig", + "tags": ["Contracts"], + "parameters": [ + { "$ref": "#/components/parameters/DeckId" }, + { + "name": "configId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "Action config deleted" }, + "404": { "description": "Not found" } + } + } + }, + "/api/images": { + "get": { + "summary": "List images", + "description": "List all images for your organization with pagination.", + "operationId": "listImages", + "tags": ["Images"], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { "type": "integer", "default": 50 } + }, + { + "name": "offset", + "in": "query", + "schema": { "type": "integer", "default": 0 } + } + ], + "responses": { + "200": { + "description": "Paginated image list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { "$ref": "#/components/schemas/Image" } + }, + "pagination": { + "type": "object", + "properties": { + "limit": { "type": "integer" }, + "offset": { "type": "integer" }, + "count": { "type": "integer" } + } + } + } + } + } + } + } + } + } + }, + "/api/images/upload": { + "post": { + "summary": "Upload an image", + "description": "Upload and optimize an image (max 10 MB). Supports JPEG, PNG, WebP, and SVG. Images are automatically resized and compressed.", + "operationId": "uploadImage", + "tags": ["Images"], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": ["file"], + "properties": { + "file": { "type": "string", "format": "binary" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Image uploaded", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "image": { "$ref": "#/components/schemas/Image" }, + "optimization": { + "type": "object", + "properties": { + "originalSize": { "type": "integer" }, + "optimizedSize": { "type": "integer" }, + "savings": { "type": "string", "example": "45%" } + } + } + } + } + } + } + }, + "400": { "description": "Invalid file type or size exceeds 10 MB" } + } + } + }, + "/api/images/{imageId}": { + "delete": { + "summary": "Delete an image", + "description": "Remove an image from storage and the database.", + "operationId": "deleteImage", + "tags": ["Images"], + "parameters": [ + { + "name": "imageId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "Image deleted" }, + "404": { "description": "Image not found" } + } + } + }, + "/api/notifications/subscriptions": { + "get": { + "summary": "List notification subscriptions", + "description": "List all notification subscriptions for your organization.", + "operationId": "listSubscriptions", + "tags": ["Notifications"], + "responses": { + "200": { + "description": "List of subscriptions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/NotificationSubscription" } + } + } + } + } + } + }, + "post": { + "summary": "Create a subscription", + "description": "Subscribe to events via webhook, email, or console. Configure which event types to receive and the delivery channel.", + "operationId": "createSubscription", + "tags": ["Notifications"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name", "eventType", "channel", "channelConfig"], + "properties": { + "name": { "type": "string", "example": "Slack webhook for contract signed" }, + "eventType": { "type": "string", "example": "contract.signed" }, + "channel": { "type": "string", "enum": ["webhook", "email", "console"] }, + "channelConfig": { + "type": "object", + "description": "Channel-specific configuration. Webhook: `{ url, headers }`. Email: `{ toEmails, fromEmail }`." + }, + "deckId": { "type": "string", "format": "uuid", "description": "Scope to a specific deck" }, + "isActive": { "type": "boolean", "default": true } + } + } + } + } + }, + "responses": { + "201": { + "description": "Subscription created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + }, + "400": { "description": "Invalid configuration" } + } + } + }, + "/api/notifications/subscriptions/{subscriptionId}": { + "get": { + "summary": "Get a subscription", + "description": "Retrieve a notification subscription by ID.", + "operationId": "getSubscription", + "tags": ["Notifications"], + "parameters": [ + { + "name": "subscriptionId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { + "description": "Subscription details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + }, + "404": { "description": "Not found" } + } + }, + "put": { + "summary": "Update a subscription", + "description": "Update a notification subscription's name, event type, channel, config, scope, or active status.", + "operationId": "updateSubscription", + "tags": ["Notifications"], + "parameters": [ + { + "name": "subscriptionId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "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" }, + "isActive": { "type": "boolean" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Subscription updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationSubscription" + } + } + } + } + } + }, + "delete": { + "summary": "Delete a subscription", + "description": "Remove a notification subscription.", + "operationId": "deleteSubscription", + "tags": ["Notifications"], + "parameters": [ + { + "name": "subscriptionId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "Subscription deleted" }, + "404": { "description": "Not found" } + } + } + }, + "/api/notifications/events": { + "get": { + "summary": "List events", + "description": "Query the event log for your organization with optional filtering by event type, aggregate, and date range.", + "operationId": "listEvents", + "tags": ["Notifications"], + "parameters": [ + { "name": "eventType", "in": "query", "schema": { "type": "string" } }, + { "name": "aggregateType", "in": "query", "schema": { "type": "string" } }, + { "name": "aggregateId", "in": "query", "schema": { "type": "string" } }, + { "name": "since", "in": "query", "schema": { "type": "string", "format": "date-time" } }, + { "name": "until", "in": "query", "schema": { "type": "string", "format": "date-time" } }, + { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } }, + { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } } + ], + "responses": { + "200": { + "description": "Paginated event list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "events": { "type": "array", "items": { "type": "object" } }, + "total": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/company/contact": { + "get": { + "summary": "Get company contact", + "description": "Retrieve your organization's contact information.", + "operationId": "getCompanyContact", + "tags": ["Company"], + "responses": { + "200": { + "description": "Company contact info", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contact": { "$ref": "#/components/schemas/CompanyContact" }, + "organizationName": { "type": "string" } + } + } + } + } + } + } + }, + "put": { + "summary": "Update company contact", + "description": "Update your organization's contact information.", + "operationId": "updateCompanyContact", + "tags": ["Company"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CompanyContact" } + } + } + }, + "responses": { + "200": { + "description": "Contact updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "contact": { "$ref": "#/components/schemas/CompanyContact" } + } + } + } + } + } + } + } + }, + "/api/company/kb/profile": { + "get": { + "summary": "Get knowledge base profile", + "description": "Retrieve your organization's knowledge base profile used for AI-powered Q&A responses.", + "operationId": "getKBProfile", + "tags": ["Company"], + "responses": { + "200": { + "description": "KB profile", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "profile": { "$ref": "#/components/schemas/KBProfile" } + } + } + } + } + } + } + }, + "put": { + "summary": "Update knowledge base profile", + "description": "Create or update your organization's knowledge base profile.", + "operationId": "updateKBProfile", + "tags": ["Company"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/KBProfile" } + } + } + }, + "responses": { + "200": { + "description": "KB profile updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "profile": { "$ref": "#/components/schemas/KBProfile" } + } + } + } + } + } + } + } + }, + "/api/company/kb/documents": { + "get": { + "summary": "List KB documents", + "description": "List all knowledge base documents for your organization.", + "operationId": "listKBDocuments", + "tags": ["Company"], + "responses": { + "200": { + "description": "KB documents", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "documents": { "type": "array", "items": { "type": "object" } }, + "count": { "type": "integer" }, + "remaining": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/company/kb/documents/upload-init": { + "post": { + "summary": "Initialize KB document upload", + "description": "Start a knowledge base document upload. Returns a presigned S3 upload URL. Validates file type, size, and document count limits.", + "operationId": "initKBDocumentUpload", + "tags": ["Company"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["fileName", "fileType", "fileSize"], + "properties": { + "fileName": { "type": "string", "example": "product-catalog.pdf" }, + "fileType": { "type": "string", "example": "application/pdf" }, + "fileSize": { "type": "integer", "example": 2048000 } + } + } + } + } + }, + "responses": { + "200": { + "description": "Upload URL generated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "documentId": { "type": "string", "format": "uuid" }, + "uploadUrl": { "type": "string" } + } + } + } + } + } + } + } + }, + "/api/company/kb/documents/complete": { + "post": { + "summary": "Complete KB document upload", + "description": "Mark a document upload as complete and trigger background processing (chunking, embedding).", + "operationId": "completeKBDocumentUpload", + "tags": ["Company"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["documentId"], + "properties": { + "documentId": { "type": "string", "format": "uuid" } + } + } + } + } + }, + "responses": { + "200": { "description": "Upload finalized and processing started" } + } + } + }, + "/api/company/kb/documents/{documentId}": { + "get": { + "summary": "Get a KB document", + "description": "Retrieve a single knowledge base document by ID.", + "operationId": "getKBDocument", + "tags": ["Company"], + "parameters": [ + { + "name": "documentId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "KB document details" }, + "404": { "description": "Not found" } + } + }, + "delete": { + "summary": "Delete a KB document", + "description": "Remove a knowledge base document from storage and the database.", + "operationId": "deleteKBDocument", + "tags": ["Company"], + "parameters": [ + { + "name": "documentId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { "description": "Document deleted" }, + "404": { "description": "Not found" } + } + } + }, + "/api/contracts/import": { + "post": { + "summary": "Import a contract document", + "description": "Upload a contract document (PDF or Word, max 10 MB) for AI analysis. Extracts variables and template structure for use as a contract template.", + "operationId": "importContract", + "tags": ["Contracts"], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": ["file"], + "properties": { + "file": { "type": "string", "format": "binary" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Contract analyzed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "variables": { "type": "array", "items": { "type": "string" } }, + "template": { "type": "string" } + } + } + } + } + }, + "400": { "description": "Invalid file type or too large" } + } + } + }, + "/api/config/maps-key": { + "get": { + "summary": "Get Maps API key", + "description": "Retrieve the Google Maps API key for client-side address autocomplete. Returns `null` if not configured.", + "operationId": "getMapsKey", + "tags": ["Config"], + "responses": { + "200": { + "description": "Maps API key", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { "type": "string", "nullable": true } + } + } + } + } + } + } + } + }, + "/api/address/validate": { + "post": { + "summary": "Validate an address", + "description": "Validate an address via geocoding. Returns coordinates, formatted address, and normalized fields.", + "operationId": "validateAddress", + "tags": ["Address"], + "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": "Denver" }, + "state": { "type": "string", "example": "CO" }, + "zip": { "type": "string", "example": "80202" }, + "country": { "type": "string", "example": "US", "description": "ISO 3166-1 alpha-2 country code" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Validation result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { "type": "boolean" }, + "formattedAddress": { "type": "string" }, + "lat": { "type": "number" }, + "lng": { "type": "number" } + } + } + } + } + }, + "503": { "description": "Address validation service not configured" } + } + } + }, + "/api/roof-measurements": { + "post": { + "summary": "Start roof measurement", + "description": "Request a roof measurement for a homeowner's address. Validates the address and creates a pending measurement request.", + "operationId": "startRoofMeasurement", + "tags": ["Roof measurements"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["deckId", "homeowner"], + "properties": { + "deckId": { "type": "string", "format": "uuid" }, + "homeowner": { "$ref": "#/components/schemas/HomeownerData" } + } + } + } + } + }, + "responses": { + "201": { + "description": "Measurement request created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "requestId": { "type": "string", "format": "uuid" } + } + } + } + } + } + } + } + }, + "/api/roof-measurements/{requestId}/status": { + "get": { + "summary": "Get measurement status", + "description": "Poll the status of a roof measurement request. Each poll processes one orchestration step.", + "operationId": "getMeasurementStatus", + "tags": ["Roof measurements"], + "parameters": [ + { + "name": "requestId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "responses": { + "200": { + "description": "Measurement status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { "type": "string" }, + "result": { + "type": "object", + "properties": { + "squares": { "type": "number" }, + "source": { "type": "string" } + } + }, + "attempts": { "type": "integer" }, + "elapsedMs": { "type": "integer" } + } + } + } + } + } + } + } + }, + "/api/roof-measurements/{requestId}/cancel": { + "post": { + "summary": "Cancel measurement", + "description": "Cancel or skip a roof measurement request. Use `cancel` to abort entirely or `skip` to allow manual entry.", + "operationId": "cancelMeasurement", + "tags": ["Roof measurements"], + "parameters": [ + { + "name": "requestId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["action"], + "properties": { + "action": { "type": "string", "enum": ["cancel", "skip"] } + } + } + } + } + }, + "responses": { + "200": { "description": "Measurement cancelled or skipped" } + } + } + }, + "/api/voices/list": { + "get": { + "summary": "List voices", + "description": "List available TTS voices curated for sales presentations.", + "operationId": "listVoices", + "tags": ["Voices"], + "responses": { + "200": { + "description": "Available voices", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "voices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "voiceId": { "type": "string" }, + "name": { "type": "string" }, + "description": { "type": "string" }, + "previewUrl": { "type": "string" } + } + } + } + } + } + } + } + } + } + } + }, + "/api/fonts": { + "get": { + "summary": "List fonts", + "description": "List available Google Fonts for deck branding.", + "operationId": "listFonts", + "tags": ["Fonts"], + "responses": { + "200": { + "description": "Available fonts", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "fonts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "family": { "type": "string", "example": "Inter" }, + "category": { "type": "string", "example": "sans-serif" } + } + } + } + } + } + } + } + } + } + } + }, + "/api/demand-iq/settings": { + "get": { + "summary": "Get Demand IQ settings", + "description": "Retrieve company settings from the Demand IQ platform, including pricing defaults.", + "operationId": "getDemandIQSettings", + "tags": ["Config"], + "responses": { + "200": { "description": "Demand IQ settings" }, + "503": { "description": "Demand IQ integration not configured" } + } + } + } + }, + "components": { + "parameters": { + "DeckId": { + "name": "deckId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" }, + "description": "Deck ID" + }, + "SlideId": { + "name": "slideId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" }, + "description": "Slide ID" + }, + "PresentationId": { + "name": "presentationId", + "in": "path", + "required": true, + "schema": { "type": "string", "format": "uuid" }, + "description": "Presentation ID" + } + }, + "schemas": { + "AuthResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "email": { "type": "string", "format": "email" }, + "name": { "type": "string", "nullable": true } + } + }, + "organization": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "name": { "type": "string" } + } + } + } + }, + "Deck": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "organizationId": { "type": "string", "format": "uuid" }, + "name": { "type": "string", "example": "Solar Roof Pitch" }, + "description": { "type": "string", "nullable": true }, + "type": { "type": "string", "enum": ["structured", "image-based"] }, + "isActive": { "type": "boolean" }, + "metadata": { "type": "object", "nullable": true }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "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" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" }, + "previewImageUrl": { "type": "string", "nullable": true } + } + }, + "Slide": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "slideId": { "type": "string" }, + "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 }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" } }, + "orderIndex": { "type": "integer" }, + "slideType": { "type": "string", "enum": ["standard", "product_pricing_v1"] }, + "productData": { "$ref": "#/components/schemas/ProductSlideData" } + } + }, + "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 }, + "wordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" }, "nullable": true }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "Branding": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "logo": { "type": "string" }, + "avatar": { "type": "string" }, + "chatName": { "type": "string" }, + "chatGreeting": { "type": "string" }, + "palette": { + "type": "object", + "properties": { + "primary": { "type": "string" }, + "primaryLight": { "type": "string" }, + "accent": { "type": "string" }, + "neutral": { "type": "string" }, + "text": { "type": "string" } + } + }, + "fonts": { "type": "object" }, + "cta": { + "type": "object", + "properties": { + "headline": { "type": "string" }, + "body": { "type": "string" }, + "buttonText": { "type": "string" } + } + }, + "contact": { "type": "object" }, + "loi": { "type": "object" } + } + }, + "QASettings": { + "type": "object", + "properties": { + "resumePromptText": { "type": "string" }, + "resumePromptAudioUrl": { "type": "string", "nullable": true }, + "resumePromptWordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" }, "nullable": true }, + "transitionPromptText": { "type": "string" }, + "transitionPromptAudioUrl": { "type": "string", "nullable": true }, + "transitionPromptWordTimings": { "type": "array", "items": { "$ref": "#/components/schemas/WordTiming" }, "nullable": true } + } + }, + "WordTiming": { + "type": "object", + "properties": { + "word": { "type": "string" }, + "startTime": { "type": "number" }, + "endTime": { "type": "number" } + } + }, + "HomeownerData": { + "type": "object", + "required": ["first_name", "address", "city", "country"], + "properties": { + "first_name": { "type": "string", "example": "Sarah" }, + "last_name": { "type": "string", "example": "Johnson" }, + "address": { "type": "string", "example": "123 Main St" }, + "city": { "type": "string", "example": "Denver" }, + "state": { "type": "string", "example": "CO" }, + "zip": { "type": "string", "example": "80202" }, + "country": { "type": "string", "example": "US" }, + "phone": { "type": "string", "example": "+13035551234" }, + "email": { "type": "string", "format": "email", "example": "sarah@example.com" } + } + }, + "VoiceSettings": { + "type": "object", + "properties": { + "voice_id": { "type": "string" }, + "stability": { "type": "number" }, + "similarity_boost": { "type": "number" } + } + }, + "LanguageSettings": { + "type": "object", + "properties": { + "language_code": { "type": "string", "example": "en" } + } + }, + "PresentationSummary": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "deckId": { "type": "string", "format": "uuid" }, + "status": { "type": "string", "enum": ["not_started", "pending", "generating", "ready", "failed"] }, + "homeownerData": { "$ref": "#/components/schemas/HomeownerData" }, + "voiceSettings": { "$ref": "#/components/schemas/VoiceSettings" }, + "languageSettings": { "$ref": "#/components/schemas/LanguageSettings" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "Image": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "fileName": { "type": "string" }, + "originalFileName": { "type": "string" }, + "mimeType": { "type": "string" }, + "fileSize": { "type": "integer" }, + "width": { "type": "integer" }, + "height": { "type": "integer" }, + "s3Url": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" } + } + }, + "NotificationSubscription": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "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" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "CompanyContact": { + "type": "object", + "properties": { + "phone": { "type": "string", "example": "+13035551234" }, + "email": { "type": "string", "format": "email", "example": "info@acmeroofing.com" }, + "address": { "type": "string" }, + "city": { "type": "string" }, + "state": { "type": "string" }, + "zip": { "type": "string" }, + "licenseNumber": { "type": "string" }, + "timezone": { "type": "string", "example": "America/Denver" } + } + }, + "KBProfile": { + "type": "object", + "properties": { + "tagline": { "type": "string", "example": "Denver's trusted solar installer since 2015" }, + "about": { "type": "string" }, + "trustSignals": { "type": "string" }, + "serviceCoverage": { "type": "string" }, + "answerStyle": { "type": "string" } + } + }, + "PriceVariable": { + "type": "object", + "properties": { + "name": { "type": "string", "example": "roof_squares" }, + "label": { "type": "string", "example": "Roof squares" }, + "type": { "type": "string", "enum": ["number"] }, + "defaultValue": { "type": "number" }, + "required": { "type": "boolean" }, + "helpText": { "type": "string" }, + "source": { "type": "string", "enum": ["measurement", "manual"] }, + "systemVariable": { "type": "boolean" } + } + }, + "PriceFormatting": { + "type": "object", + "properties": { + "currency": { "type": "string", "example": "USD" }, + "roundTo": { "type": "number", "example": 2 } + } + }, + "ProductSlideData": { + "type": "object", + "properties": { + "subtitle": { "type": "string" }, + "brandLogo": { "type": "string" }, + "disclaimer": { "type": "string" }, + "defaultSelectedTier": { "type": "string", "enum": ["good", "better", "best"] }, + "backgroundColor": { "type": "string" }, + "selectionColor": { "type": "string" }, + "roundTo": { "type": "number" }, + "good": { "type": "object", "description": "Good tier product card" }, + "better": { "type": "object", "description": "Better tier product card" }, + "best": { "type": "object", "description": "Best tier product card" } } } } diff --git a/quickstart.mdx b/quickstart.mdx index a5a031a..84606e5 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -1,16 +1,142 @@ --- title: Quickstart -description: "Get up and running quickly" +description: Create your first AI-narrated presentation in a few API calls. --- -## Step 1 +This guide walks you through creating a deck, adding slides, configuring branding, and generating a personalized presentation for a homeowner. -Describe the first step here. +## Prerequisites -## Step 2 +- A Sales CoPilot account with API access +- Your login credentials (email and password) -Describe the second step here. +## Authenticate -## Step 3 +Start by logging in to get a session cookie: -Describe the third step here. +```typescript +const response = await fetch("https://app.demandiq.com/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: "sales@acmeroofing.com", + password: "your-password", + }), +}); +// The session cookie is set automatically +``` + +## Create a deck + +Create a new presentation deck with a name and optional context for AI narration: + +```typescript +const deck = await fetch("https://app.demandiq.com/api/decks", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: "Solar Roof Pitch", + description: "Standard residential solar sales deck", + type: "structured", + deckContext: "Acme Solar installs residential solar panels in the Denver metro area.", + }), +}).then((r) => r.json()); +``` + +## Add slides + +Add slides to your deck with narration scripts. Use `{{first_name}}` and other tokens for personalization: + +```typescript +await fetch(`https://app.demandiq.com/api/decks/${deck.deck.id}/slides`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + slideId: "welcome", + title: "Welcome to Your Solar Journey", + narrationScript: + "Hi {{first_name}}, thanks for taking the time to learn about going solar with Acme Solar.", + bullets: [ + "Personalized proposal based on your home", + "Transparent pricing with no hidden fees", + "25-year performance warranty", + ], + }), +}); +``` + +## Configure branding + +Set your company colors, logo, and call-to-action: + +```typescript +await fetch(`https://app.demandiq.com/api/decks/${deck.deck.id}/branding`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: "Acme Solar", + palette: { + primary: "#1a73e8", + primaryLight: "#4a90d9", + accent: "#ff6b35", + neutral: "#f5f5f5", + text: "#333333", + }, + cta: { + headline: "Ready to go solar?", + body: "Schedule your free consultation today.", + buttonText: "Get started", + }, + }), +}); +``` + +## Generate a presentation + +Create a personalized presentation for a homeowner: + +```typescript +const presentation = await fetch( + `https://app.demandiq.com/api/decks/${deck.deck.id}/presentations`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + homeowner: { + first_name: "Sarah", + last_name: "Johnson", + address: "123 Main St", + city: "Denver", + state: "CO", + zip: "80202", + country: "US", + email: "sarah@example.com", + }, + }), + } +).then((r) => r.json()); + +// Share this URL with the homeowner +console.log(presentation.url); +``` + +## Poll for status + +Audio generation runs asynchronously. Poll the status endpoint until the presentation is ready: + +```typescript +let status = "pending"; +while (status !== "ready" && status !== "failed") { + const result = await fetch( + `https://app.demandiq.com/api/presentations/${presentation.deck_presentation_id}/status` + ).then((r) => r.json()); + status = result.status; + if (status !== "ready") await new Promise((r) => setTimeout(r, 2000)); +} +``` + +## Next steps + +- Add FAQs to your deck so the AI can answer common homeowner questions +- Configure notification subscriptions to get webhooks when homeowners view slides or sign contracts +- Upload your company's knowledge base documents to improve Q&A answer quality