{"openapi":"3.0.0","info":{"title":"Exoserva Enterprise API","version":"3.0.0","description":"# Exoserva Platform API AI-native operations platform for field service contractors — HVAC, plumbing, and electrical. ## Authentication All API endpoints (except /api/auth/* and /api/health/*) require Bearer token authentication. ```\nAuthorization: Bearer <access_token>\n``` ## Multi-tenancy This API is multi-tenant. Each request is scoped to a tenant based on:\n- JWT token (contains tenantId)\n- X-Tenant-ID header (optional override) ## Rate Limiting - Standard: 100 requests per minute\n- Auth endpoints: 10 requests per minute\n- Webhook endpoints: 1000 requests per minute ## Error Responses All errors follow a consistent format:\n```json\n{ \"success\": false, \"error\": { \"code\": \"ERROR_CODE\", \"message\": \"Human-readable message\" }\n}\n```","contact":{"name":"Exoserva Support","email":"contact@exoserva.com","url":"https://exoserva.com"},"license":{"name":"Proprietary","url":"https://exoserva.com/terms"}},"servers":[{"url":"https://api.exoserva.com","description":"Production"}],"tags":[{"name":"A/B Testing","description":"A/B test experiments and variant management"},{"name":"Accounts","description":"Customer account management"},{"name":"Agents","description":"AI agent status, configuration, and logs"},{"name":"AI Costs","description":"AI usage cost tracking and budget management"},{"name":"AI Maintenance","description":"AI-powered maintenance triage and analysis"},{"name":"AI Steering","description":"AI model steering commands and configuration"},{"name":"Analytics","description":"Business analytics, reports, and correlations"},{"name":"Authentication","description":"Authentication, login, SSO, and token management"},{"name":"Billing","description":"Subscription billing, plans, seats, and payment methods"},{"name":"Branding","description":"White-label branding, logos, domains, and email templates"},{"name":"Cache","description":"Cache management and invalidation"},{"name":"Calendar","description":"Calendar integrations and scheduling"},{"name":"Contractor Portal","description":"Contractor portal jobs, stats, and actions"},{"name":"Customers","description":"Customer records, notes, and portal access"},{"name":"Dispatch","description":"Dispatch assignments, skills, locations, and notifications"},{"name":"Documentation Agent","description":"AI documentation query and feedback"},{"name":"Emergency Detection","description":"AI-powered emergency detection and analysis"},{"name":"Follow-Ups","description":"Follow-up queue management and automation"},{"name":"Health","description":"Health checks, liveness, and readiness probes"},{"name":"Invoices","description":"Invoice CRUD, actions, payments, and reports"},{"name":"Jobs","description":"Job scheduling and management"},{"name":"Maintenance Schedules","description":"Preventive maintenance schedules and metrics"},{"name":"Maintenance Templates","description":"Maintenance task templates"},{"name":"Notifications","description":"Notification list, actions, and unread counts"},{"name":"Organizations","description":"Organization hierarchy and management"},{"name":"Payments","description":"Payment processing and records"},{"name":"Photo Analysis","description":"AI-powered photo analysis for maintenance"},{"name":"Platform Admin","description":"Platform administration and monitoring"},{"name":"Portal","description":"Tenant portal auth, work orders, and service requests"},{"name":"PriceBook","description":"Price book items, categories, and materials"},{"name":"QuickBooks","description":"QuickBooks integration and sync"},{"name":"Reports","description":"Business reports and data exports"},{"name":"Safety Standards","description":"Safety standards and OSHA compliance"},{"name":"Schedule","description":"Scheduling slots, optimization, and service durations"},{"name":"Search","description":"Global search across entities"},{"name":"Signatures","description":"Digital signature requests and provider webhooks"},{"name":"Subscription","description":"Subscription management and status"},{"name":"Tenants","description":"Tenant (renter) CRUD and actions"},{"name":"Territories","description":"Service territory management and technician assignments"},{"name":"Time Tracking","description":"Time entries, clock in/out, breaks, timesheets, and payroll"},{"name":"Troubleshooting","description":"AI-guided troubleshooting sessions and flows"},{"name":"Users","description":"User CRUD, roles, preferences, and profile"},{"name":"Vendor Intelligence","description":"AI vendor analysis and insights"},{"name":"Vendor Portal","description":"Vendor self-service portal"},{"name":"Vendors","description":"Vendor management"},{"name":"Voice","description":"Voice AI settings, calls, and analytics"},{"name":"Webhooks","description":"External webhook integrations (Twilio, Stripe, etc.)"},{"name":"Work Orders","description":"Work order management and SLAs"},{"name":"Workflows","description":"Workflow automation engine"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT access token obtained from /api/auth/login"}},"schemas":{"Error":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"Invalid input provided"}}}}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object"}}},"Pagination":{"type":"object","properties":{"page":{"type":"integer","example":1},"limit":{"type":"integer","example":20},"total":{"type":"integer","example":100},"totalPages":{"type":"integer","example":5}}},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"firstName":{"type":"string"},"lastName":{"type":"string"},"status":{"type":"string","enum":["active","inactive","pending"]},"roles":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"}}},"Conversation":{"type":"object","properties":{"id":{"type":"integer"},"tenant_id":{"type":"integer"},"lead_phone":{"type":"string"},"lead_name":{"type":"string"},"channel":{"type":"string","enum":["sms","voice","web","email"]},"status":{"type":"string","enum":["active","resolved","archived"]},"mode":{"type":"string","enum":["ai","human","hybrid"]},"ai_summary":{"type":"string"},"lead_score":{"type":"integer","minimum":0,"maximum":100},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"WorkOrder":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenant_id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["draft","pending","scheduled","in_progress","completed","cancelled"]},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"category":{"type":"string"},"property_id":{"type":"string","format":"uuid"},"scheduled_date":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Invoice":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenant_id":{"type":"string"},"invoice_number":{"type":"string"},"account_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["draft","pending","partial","paid","overdue","cancelled"]},"subtotal_cents":{"type":"integer"},"tax_cents":{"type":"integer"},"total_cents":{"type":"integer"},"balance_due_cents":{"type":"integer"},"due_date":{"type":"string","format":"date"},"created_at":{"type":"string","format":"date-time"}}},"Payment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenant_id":{"type":"string"},"account_id":{"type":"string","format":"uuid"},"amount_cents":{"type":"integer"},"payment_method":{"type":"string","enum":["cash","check","credit_card","debit_card","ach","wire_transfer","stripe","paypal","venmo","zelle","other"]},"status":{"type":"string","enum":["pending","processing","completed","failed","refunded","partially_refunded","cancelled"]},"created_at":{"type":"string","format":"date-time"}}},"Property":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenant_id":{"type":"string"},"name":{"type":"string"},"address":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"},"type":{"type":"string","enum":["residential","commercial","mixed"]},"status":{"type":"string","enum":["active","inactive"]},"created_at":{"type":"string","format":"date-time"}}},"HealthCheck":{"type":"object","properties":{"status":{"type":"string","enum":["healthy","degraded","unhealthy"]},"timestamp":{"type":"string","format":"date-time"},"uptime":{"type":"number","description":"Uptime in seconds"},"version":{"type":"string"},"components":{"type":"object","properties":{"database":{"type":"object"},"redis":{"type":"object"}}}}},"AIModelConfig":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"agentType":{"type":"string","example":"sales"},"taskType":{"type":"string","example":"complex"},"modelId":{"type":"string","enum":["claude-opus-4-5-20251101","claude-sonnet-4-6","claude-haiku-4-5-20251001"]},"strategy":{"type":"string","enum":["cost","quality","balanced","custom"]},"maxTokens":{"type":"integer","example":2000},"temperature":{"type":"number","example":0.7},"enabled":{"type":"boolean"},"priority":{"type":"integer","example":100},"createdAt":{"type":"string","format":"date-time"}}},"EmailTrackingEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"emailId":{"type":"string"},"recipient":{"type":"string","format":"email"},"eventType":{"type":"string","enum":["sent","delivered","opened","clicked","bounced","complained"]},"url":{"type":"string","format":"uri"},"userAgent":{"type":"string"},"ipAddress":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"EmailTrackingStats":{"type":"object","properties":{"totalSent":{"type":"integer"},"delivered":{"type":"integer"},"opened":{"type":"integer"},"clicked":{"type":"integer"},"bounced":{"type":"integer"},"openRate":{"type":"number","example":45.5},"clickRate":{"type":"number","example":12.3},"bounceRate":{"type":"number","example":2.1}}},"Workflow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tenantId":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"trigger":{"type":"string","enum":["manual","schedule","event","webhook"]},"status":{"type":"string","enum":["draft","active","paused","archived"]},"steps":{"type":"array","items":{"type":"object"}},"createdAt":{"type":"string","format":"date-time"}}},"AICostSummary":{"type":"object","properties":{"totalCost":{"type":"number","example":125.5},"totalTokens":{"type":"integer","example":1250000},"requestCount":{"type":"integer","example":5000},"avgCostPerRequest":{"type":"number","example":0.025},"byModel":{"type":"object","additionalProperties":{"type":"object","properties":{"cost":{"type":"number"},"tokens":{"type":"integer"},"requests":{"type":"integer"}}}}}}},"responses":{"Unauthorized":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"success":false,"error":{"code":"UNAUTHORIZED","message":"Authentication required"}}}}},"Forbidden":{"description":"Insufficient permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"success":false,"error":{"code":"FORBIDDEN","message":"Insufficient permissions"}}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"success":false,"error":{"code":"NOT_FOUND","message":"Resource not found"}}}}},"ValidationError":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid input"}}}}}}},"security":[{"bearerAuth":[]}],"paths":{"/v3/ab-auto-selection/analyze/{experimentId}":{"get":{"tags":["A/B Testing"],"summary":"Analyze experiment for auto-selection","description":"Get detailed analysis of an experiment including statistical significance","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"experimentId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Experiment analysis"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v3/ab-auto-selection/config":{"get":{"tags":["A/B Testing"],"summary":"Get auto-selection configuration","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current configuration"}}},"put":{"tags":["A/B Testing"],"summary":"Update auto-selection configuration","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"minSampleSize":{"type":"integer","minimum":10},"minSignificance":{"type":"number","minimum":0.5,"maximum":0.99},"minDurationDays":{"type":"integer","minimum":1},"primaryMetric":{"type":"string","enum":["conversion","quality","combined"]}}}}}},"responses":{"200":{"description":"Configuration updated"}}}},"/v3/ab-auto-selection/eligible":{"get":{"tags":["A/B Testing"],"summary":"Get experiments eligible for auto-selection","description":"List all experiments that meet criteria for automatic winner selection","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of eligible experiments"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v3/ab-auto-selection/run":{"post":{"tags":["A/B Testing"],"summary":"Run auto-selection for all eligible experiments","description":"Process all eligible experiments and auto-select winners","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Auto-selection results"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v3/ab-auto-selection/select/{experimentId}":{"post":{"tags":["A/B Testing"],"summary":"Auto-select winner for experiment","description":"Automatically select the winning variant and end the experiment","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"experimentId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Winner selected"},"400":{"description":"Cannot auto-select (not enough data or not significant)"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/ab-tests/{name}":{"get":{"tags":["A/B Testing"],"summary":"Get experiment by name","description":"Retrieve a single A/B test experiment by its unique name, including variant configuration and current status.","parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"},"description":"Unique experiment name"}],"responses":{"200":{"description":"Experiment details"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"},"404":{"description":"Experiment not found"}}},"put":{"tags":["A/B Testing"],"summary":"Update an existing experiment by name","description":"Update an A/B experiment's configuration, status, or variant definitions. Use this to pause, resume, or adjust traffic splits.","parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"},"description":"Unique experiment name"}],"responses":{"200":{"description":"Experiment updated successfully"},"400":{"description":"Validation error — invalid update data"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin or owner role"},"404":{"description":"Experiment not found"}}},"delete":{"tags":["A/B Testing"],"summary":"Delete an experiment by name","description":"Permanently delete an A/B test experiment and all associated assignments and events. This action cannot be undone.","parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"},"description":"Unique experiment name"}],"responses":{"200":{"description":"Experiment deleted successfully"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin or owner role"},"404":{"description":"Experiment not found"}}}},"/api/v3/ab-tests/{name}/stats":{"get":{"tags":["A/B Testing"],"summary":"Get experiment statistics by name","description":"Retrieve aggregated statistical results for an A/B experiment, including conversion rates and winning variant data.","parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"},"description":"Unique experiment name"}],"responses":{"200":{"description":"Experiment statistics"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"},"404":{"description":"Experiment not found"}}}},"/api/v3/ab-tests/events":{"post":{"tags":["A/B Testing"],"summary":"Log experiment event","description":"Record a conversion or interaction event for an A/B test assignment. Use this to track user actions that determine the winning variant.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignmentId","eventType"]}}}},"responses":{"200":{"description":"Event logged successfully"},"400":{"description":"Validation error — assignmentId or eventType missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ab-tests":{"get":{"tags":["A/B Testing"],"summary":"List all experiments","description":"Get all A/B test experiments for the tenant with optional status filtering and pagination. Returns experiments ordered by creation date descending.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"status","schema":{"type":"string","enum":["active","paused","completed"]},"description":"Filter by experiment status"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Maximum number of experiments to return"},{"in":"query","name":"offset","schema":{"type":"integer","default":0},"description":"Number of experiments to skip for pagination"}],"responses":{"200":{"description":"List of experiments with pagination metadata"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, manager, or owner role"}}},"post":{"tags":["A/B Testing"],"summary":"Create a new A/B experiment","description":"Create a new A/B test experiment with two variants (A and B), traffic split configuration, and targeting rules.","responses":{"201":{"description":"Experiment created successfully"},"400":{"description":"Validation error — invalid experiment configuration"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin or owner role"}}}},"/api/v3/agents/analytics/analyze":{"post":{"tags":["Agents"],"summary":"Analyze service profitability with AI data analyst agent","description":"Invoke the AI data analyst agent to analyze service profitability for a given date range, returning insights, metrics, and recommendations.","responses":{"200":{"description":"AI profitability analysis with insights and recommendations"},"400":{"description":"Validation error — invalid date range"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/analytics/full":{"get":{"tags":["Agents"],"summary":"Get full AI-generated analytics report","description":"Generate a comprehensive AI analytics report for a specified period, including full narrative content, data snapshots, and token usage statistics.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["week","month","quarter","year"]},"description":"Reporting period for the analytics report"}],"responses":{"200":{"description":"Full AI analytics report with content and data snapshot"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/config":{"get":{"tags":["Agents"],"summary":"List all agent configurations","description":"Retrieve configurations for all AI agents including enabled/disabled status, model settings, and behavioral parameters.","responses":{"200":{"description":"List of agent configurations"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/config/{agentId}":{"get":{"tags":["Agents"],"summary":"Get a specific agent configuration","description":"Retrieve the configuration for a single AI agent by its ID.","parameters":[{"in":"path","name":"agentId","required":true,"schema":{"type":"string"},"description":"Agent identifier (e.g. sales, scheduling, analyst)"}],"responses":{"200":{"description":"Agent configuration details"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Agent configuration not found"}}},"patch":{"tags":["Agents"],"summary":"Update an agent configuration","description":"Update behavioral settings for a specific AI agent, such as model temperature, system prompt overrides, or enabled features.","parameters":[{"in":"path","name":"agentId","required":true,"schema":{"type":"string"},"description":"Agent identifier"}],"responses":{"200":{"description":"Updated agent configuration"},"400":{"description":"Validation error — invalid configuration data"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Agent configuration not found"}}}},"/api/v3/agents/feedback":{"post":{"tags":["Agents"],"summary":"Submit feedback for an AI agent response","description":"Record user feedback (rating 1-5 with optional comment) for a specific AI agent message. Used to improve agent performance over time.","responses":{"200":{"description":"Feedback recorded with feedback ID"},"400":{"description":"Validation error — invalid rating, missing conversation ID, or agent type"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/feedback/stats":{"get":{"tags":["Agents"],"summary":"Get AI agent feedback statistics","description":"Retrieve aggregated feedback statistics for AI agents, including average ratings, rating distributions, and trend data. Filterable by agent type and time window.","parameters":[{"in":"query","name":"agent_type","schema":{"type":"string","enum":["sales","scheduling","analyst","property","orchestrator"]},"description":"Filter statistics by agent type"},{"in":"query","name":"days_back","schema":{"type":"integer"},"description":"Number of days to include in statistics (default 30)"}],"responses":{"200":{"description":"Aggregated agent feedback statistics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/property/chat":{"post":{"tags":["Agents"],"summary":"Chat with the property management AI agent","description":"Send a message to the property management AI agent on behalf of a resident. The agent handles maintenance requests, work order creation, and resident inquiries with automatic escalation for complex issues.","responses":{"200":{"description":"Agent response with intent, actions, and optional work order details"},"400":{"description":"Validation error — required fields missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/scheduling/chat":{"post":{"tags":["Agents"],"summary":"Chat with the scheduling AI agent","description":"Send a message to the scheduling AI agent to get dispatch recommendations, conflict resolution suggestions, and technician assignment assistance.","responses":{"200":{"description":"Scheduling agent response with suggestions"},"400":{"description":"Validation error — message or conversation_id missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/scheduling/briefing":{"get":{"tags":["Agents"],"summary":"Get AI-generated daily scheduling briefing","description":"Retrieve an AI-generated briefing for a specific date, summarizing job distribution, technician efficiency, travel time, and scheduling alerts.","parameters":[{"in":"query","name":"date","schema":{"type":"string","format":"date"},"description":"Date for the briefing (defaults to today)"},{"in":"query","name":"technician_id","schema":{"type":"string"},"description":"Filter briefing to a specific technician"}],"responses":{"200":{"description":"Daily scheduling briefing with technician summaries"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/status":{"get":{"tags":["Agents"],"summary":"Check AI agents status","description":"Retrieve the operational status of all AI agents, including online/offline state, last activity timestamps, and current availability.","responses":{"200":{"description":"Status of all AI agents"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/status/stats":{"get":{"tags":["Agents"],"summary":"Get aggregated agent statistics","description":"Retrieve aggregated usage statistics across all AI agents including message counts, average response times, and error rates.","responses":{"200":{"description":"Aggregated agent usage statistics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/status/stats/comparison":{"get":{"tags":["Agents"],"summary":"Get agent statistics with historical comparison","description":"Retrieve agent statistics compared against the previous period to identify trends in usage, performance, and error rates.","parameters":[{"name":"period","in":"query","schema":{"type":"string","enum":["day","week","month"],"default":"day"},"description":"Comparison period"}],"responses":{"200":{"description":"Agent statistics with period-over-period comparison"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/status/metrics/history":{"get":{"tags":["Agents"],"summary":"Get historical metrics time series for graphing","description":"Retrieve time-series metrics history for AI agents, suitable for rendering usage and performance trend charts.","parameters":[{"name":"period","in":"query","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"},"description":"Time window for historical data"},{"name":"agentId","in":"query","schema":{"type":"string"},"description":"Optional specific agent ID to filter metrics"}],"responses":{"200":{"description":"Time-series metrics data for the specified period"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/agents/status/logs":{"get":{"tags":["Agents"],"summary":"Get agent activity logs","description":"Retrieve recent activity logs for AI agents including request/response pairs, errors, and latency data. Use for debugging and auditing agent behavior.","responses":{"200":{"description":"Agent activity log entries"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/actions":{"post":{"tags":["AI Actions"],"summary":"Queue a new AI action for approval","description":"Submit an AI-suggested action for human review and approval before execution. Actions represent AI recommendations that require explicit user consent.","responses":{"201":{"description":"Action queued and awaiting approval"},"400":{"description":"Validation error — action type or parameters invalid"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"}}}},"/api/v3/ai/actions/bulk-approve":{"post":{"tags":["AI Actions"],"summary":"Bulk approve multiple AI actions","description":"Approve multiple pending AI actions in a single request. Useful when reviewing a batch of AI suggestions that all look correct.","responses":{"200":{"description":"Bulk approval completed with per-action results"},"400":{"description":"Validation error — invalid action IDs"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"}}}},"/api/v3/ai/actions/expire-old":{"post":{"tags":["AI Actions"],"summary":"Expire old pending AI actions","description":"Expire all pending AI actions that are past their TTL threshold. Run this as a maintenance task to clean up stale suggestions.","responses":{"200":{"description":"Old actions expired, returns count of expired actions"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin or owner role"}}}},"/api/v3/ai/actions/{id}/approve":{"post":{"tags":["AI Actions"],"summary":"Approve a specific AI action","description":"Approve a specific pending AI action, marking it as approved and scheduling it for execution.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Action UUID"}],"responses":{"200":{"description":"Action approved successfully"},"400":{"description":"Validation error — action is not in pending state"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"},"404":{"description":"Action not found"}}}},"/api/v3/ai/actions/{id}/reject":{"post":{"tags":["AI Actions"],"summary":"Reject a specific AI action","description":"Reject a pending AI action to prevent it from being executed. Rejection feedback can be used to improve future AI suggestions.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Action UUID"}],"responses":{"200":{"description":"Action rejected successfully"},"400":{"description":"Validation error — action is not in pending state"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"},"404":{"description":"Action not found"}}}},"/api/v3/ai/actions/{id}/execute":{"post":{"tags":["AI Actions"],"summary":"Execute a specific AI action immediately","description":"Execute an approved AI action immediately, bypassing any scheduled delay. Use when immediate action is required after approval.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Action UUID"}],"responses":{"200":{"description":"Action executed successfully"},"400":{"description":"Validation error — action must be in approved state before execution"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"},"404":{"description":"Action not found"}}}},"/api/v3/ai-actions":{"get":{"tags":["AI Actions"],"summary":"List all AI actions","description":"Retrieve all AI actions for the current tenant across all statuses (pending, approved, rejected, executed, expired).","responses":{"200":{"description":"List of all AI actions"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-actions/pending":{"get":{"tags":["AI Actions"],"summary":"Get pending AI actions","description":"Retrieve all AI actions waiting for human review and approval. Use this for building the action approval UI.","responses":{"200":{"description":"List of pending AI actions requiring review"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-actions/stats":{"get":{"tags":["AI Actions"],"summary":"Get AI actions statistics","description":"Retrieve aggregate statistics for AI actions including counts by status, approval rates, and execution success rates.","responses":{"200":{"description":"AI actions statistics summary"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-actions/{id}":{"get":{"tags":["AI Actions"],"summary":"Get an AI action by ID","description":"Retrieve full details of a specific AI action including its type, parameters, status history, and execution result.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Action UUID"}],"responses":{"200":{"description":"AI action details"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Action not found"}}}},"/api/v3/ai-analysis/explain":{"post":{"tags":["AI Analysis"],"summary":"Get an AI explanation for an action or entity","description":"Request a natural language explanation of why the AI is suggesting a specific action or what factors drive an entity's current status.","responses":{"200":{"description":"AI-generated explanation"},"400":{"description":"Validation error — missing entity type or action context"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/action/preview":{"post":{"tags":["AI Analysis"],"summary":"Preview an AI-suggested action before execution","description":"Preview the expected outcome and side effects of an AI-suggested action before committing to execution.","responses":{"200":{"description":"Action preview with expected changes and impact"},"400":{"description":"Validation error — invalid action parameters"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/action/execute":{"post":{"tags":["AI Analysis"],"summary":"Execute an AI-suggested action","description":"Execute an AI-suggested action that has been previewed and approved. The action is applied to the target entity immediately.","responses":{"200":{"description":"Action executed successfully with result details"},"400":{"description":"Validation error — invalid action or entity reference"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/action/undo":{"post":{"tags":["AI Analysis"],"summary":"Undo a previously executed AI action","description":"Reverse a previously executed AI action, restoring the entity to its prior state. Not all action types support undo.","responses":{"200":{"description":"Action undone successfully"},"400":{"description":"Validation error — action is not undoable or already undone"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/enterprise":{"get":{"tags":["AI Analysis"],"summary":"Get enterprise AI dashboard analysis","description":"Retrieve AI-generated analysis for the enterprise dashboard, including cross-location KPIs, strategic insights, and actionable recommendations.","responses":{"200":{"description":"Enterprise dashboard AI analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/portal/tenant":{"get":{"tags":["AI Analysis"],"summary":"Get tenant portal dashboard analysis","description":"Retrieve AI analysis tailored for the tenant portal dashboard, including tenant-specific performance metrics and operational insights.","responses":{"200":{"description":"Tenant portal AI analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/role-dashboard":{"get":{"tags":["AI Analysis"],"summary":"Get role-specific dashboard analysis","description":"Retrieve AI analysis customized for the current user's role (admin, technician, dispatcher, etc.), surfacing the most relevant metrics and actions.","responses":{"200":{"description":"Role-specific AI dashboard analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/platform-admin":{"get":{"tags":["AI Analysis"],"summary":"Get platform admin dashboard analysis","description":"Retrieve AI analysis for platform administrators covering cross-tenant metrics, system health, and platform-wide trends.","responses":{"200":{"description":"Platform admin AI analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/widget-insights":{"get":{"tags":["AI Analysis"],"summary":"Get AI insights for a dashboard widget","description":"Retrieve AI-generated insights for a specific dashboard widget, including anomaly detection and trend analysis for the widget's data.","parameters":[{"in":"query","name":"widgetId","required":true,"schema":{"type":"string"},"description":"Widget identifier"},{"in":"query","name":"page","schema":{"type":"string"},"description":"Dashboard page the widget belongs to"}],"responses":{"200":{"description":"Widget AI insights"},"400":{"description":"Validation error — widgetId missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/widget-insights/batch":{"post":{"tags":["AI Analysis"],"summary":"Get AI insights for multiple dashboard widgets in batch","description":"Fetch AI insights for multiple dashboard widgets in a single request. More efficient than individual widget requests when loading a full dashboard.","responses":{"200":{"description":"Batch widget insights keyed by widget ID"},"400":{"description":"Validation error — widget IDs missing or invalid"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/widget-insights/cache":{"delete":{"tags":["AI Analysis"],"summary":"Clear cached widget AI insights","description":"Invalidate cached AI insights for dashboard widgets, forcing fresh generation on the next request. Use after significant data changes.","responses":{"200":{"description":"Cache cleared successfully"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/vendor":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for vendors","description":"Retrieve AI-generated analysis for vendors including performance scoring, cost optimization opportunities, and relationship health indicators.","responses":{"200":{"description":"AI vendor analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/technician":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for technicians","description":"Retrieve AI-generated analysis for technicians including performance metrics, efficiency trends, skill assessments, and workload optimization recommendations.","responses":{"200":{"description":"AI technician analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/customer":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for customers","description":"Retrieve AI-generated analysis for customers including CLV predictions, churn risk scoring, segment classification, and re-engagement opportunities.","responses":{"200":{"description":"AI customer analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/property":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for properties","description":"Retrieve AI-generated analysis for properties including maintenance prediction, asset health scoring, and service history patterns.","responses":{"200":{"description":"AI property analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/job":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for jobs","description":"Retrieve AI-generated analysis for jobs including completion rate trends, duration accuracy, profitability by job type, and scheduling optimization insights.","responses":{"200":{"description":"AI job analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/inventory":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for inventory","description":"Retrieve AI-generated analysis for inventory including stock level predictions, reorder recommendations, and usage pattern insights.","responses":{"200":{"description":"AI inventory analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/work-order":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for work orders","description":"Retrieve AI-generated analysis for work orders including priority scoring, SLA risk detection, and resource allocation recommendations.","responses":{"200":{"description":"AI work order analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/pricebook":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for pricebook","description":"Retrieve AI-generated analysis for pricebook items including competitive pricing insights, margin optimization opportunities, and service bundling recommendations.","responses":{"200":{"description":"AI pricebook analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/lease":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for leases","description":"Retrieve AI-generated analysis for leases including renewal risk predictions, rent optimization insights, and portfolio performance trends.","responses":{"200":{"description":"AI lease analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/agents":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for agents","description":"Retrieve AI-generated analysis of AI agent performance including usage patterns, response quality trends, and improvement opportunities.","responses":{"200":{"description":"AI agents performance analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/tasks":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for tasks","description":"Retrieve AI-generated analysis for tasks including completion rate trends, overdue detection, and team productivity insights.","responses":{"200":{"description":"AI tasks analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/estimates":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for estimates","description":"Retrieve AI-generated analysis for estimates including conversion rate insights, pricing accuracy trends, and win/loss pattern analysis.","responses":{"200":{"description":"AI estimates analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/suggestions":{"post":{"tags":["AI Analysis"],"summary":"Get AI-powered form field suggestions","description":"Get AI-generated autocomplete suggestions for form fields based on existing data patterns, entity context, and user input.","responses":{"200":{"description":"AI field suggestions"},"400":{"description":"Validation error — form or field context missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/validate":{"post":{"tags":["AI Analysis"],"summary":"Validate a form field with AI assistance","description":"Use AI to validate a form field value against business rules, detect data quality issues, and provide improvement suggestions beyond standard schema validation.","responses":{"200":{"description":"AI validation result with suggestions"},"400":{"description":"Validation error — field or value missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/properties":{"get":{"tags":["AI Analysis"],"summary":"Get AI analysis for the properties panel","description":"Retrieve AI analysis for the properties list panel, including portfolio health scores, maintenance backlog assessment, and revenue optimization opportunities.","responses":{"200":{"description":"AI properties panel analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-analysis/property-details/{id}":{"get":{"tags":["AI Analysis"],"summary":"Get detailed AI analysis for a specific property","description":"Retrieve in-depth AI analysis for a single property, including asset condition predictions, service history patterns, and recommended maintenance actions.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Property UUID"}],"responses":{"200":{"description":"Detailed AI analysis for the property"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Property not found"}}}},"/api/v3/ai-analysis/territory":{"get":{"tags":["AI Analysis"],"summary":"Get AI territory analysis","description":"Retrieve AI-generated territory analysis including geographic demand patterns, travel time optimization opportunities, and service coverage gaps.","parameters":[{"in":"query","name":"zip","schema":{"type":"string"},"description":"Filter analysis to a specific ZIP code area"},{"in":"query","name":"radius","schema":{"type":"number"},"description":"Radius in miles for territory analysis"}],"responses":{"200":{"description":"AI territory analysis with demand and coverage insights"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-costs/budget":{"get":{"tags":["AI Costs"],"summary":"Get tenant AI budget settings","description":"Retrieve the current AI spending budget configuration for the tenant, including monthly and daily limits, alert thresholds, and hard limit settings.","responses":{"200":{"description":"Tenant AI budget configuration"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}},"put":{"tags":["AI Costs"],"summary":"Set tenant AI budget","description":"Configure the AI spending budget for the tenant. Set monthly/daily limits and alert thresholds to control AI API costs. Requires settings write permission.","responses":{"200":{"description":"Budget updated successfully"},"400":{"description":"Validation error — invalid budget values"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires settings:write permission"}}}},"/api/v3/ai-costs/budget/check":{"get":{"tags":["AI Costs"],"summary":"Check if tenant can make AI requests (budget check)","description":"Check whether the tenant has remaining AI budget capacity before making expensive AI requests. Returns allowed status and remaining budget details.","responses":{"200":{"description":"Budget check result with allowed status and remaining capacity"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-costs/platform":{"get":{"tags":["AI Costs"],"summary":"Get platform-wide AI analytics (admin only)"}},"/api/v3/ai-costs/pricing":{"get":{"tags":["AI Costs"],"summary":"Get current AI model pricing","description":"Returns pricing per 1M tokens for all supported models"}},"/api/v3/ai-costs/summary":{"get":{"tags":["AI Costs"],"summary":"Get AI cost usage summary for a period","description":"Retrieve aggregated AI cost usage summary for a billing period including total spend, token counts, and cost by model.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["current_month","last_month","7d","30d"]},"description":"Billing period to summarize"}],"responses":{"200":{"description":"AI cost usage summary"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-costs/daily":{"get":{"tags":["AI Costs"],"summary":"Get daily AI cost usage breakdown","description":"Retrieve day-by-day AI cost breakdown for a date range, useful for rendering spend trend charts.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start date for the breakdown"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End date for the breakdown"}],"responses":{"200":{"description":"Daily AI cost breakdown"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-costs/by-agent":{"get":{"tags":["AI Costs"],"summary":"Get AI cost usage broken down by agent","description":"Retrieve AI cost breakdown by agent type (sales, scheduling, analyst, etc.) to understand which agents are driving the most spend.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start date for the breakdown"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End date for the breakdown"}],"responses":{"200":{"description":"AI cost breakdown by agent type"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai-costs/records":{"get":{"tags":["AI Costs"],"summary":"Get individual AI cost usage records","description":"Retrieve individual AI API call cost records for detailed auditing, including model used, token counts, and cost per request.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start date filter"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End date filter"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of records to return"},{"in":"query","name":"offset","schema":{"type":"integer"},"description":"Pagination offset"}],"responses":{"200":{"description":"Individual AI cost records"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/financial/automation-rules":{"get":{"tags":["AI Financial"],"summary":"List the current tenant's invoice automation rules","responses":{"200":{"description":"List of automation rules"}}},"post":{"tags":["AI Financial"],"summary":"Create a new automation rule for the current tenant","responses":{"201":{"description":"Rule created"},"400":{"description":"Validation failed"}}}},"/api/v3/ai/financial/automation-rules/{id}":{"put":{"tags":["AI Financial"],"summary":"Replace an automation rule (partial; tenant-scoped)","responses":{"200":{"description":"Rule updated"},"404":{"description":"Rule not found for this tenant"}}},"patch":{"tags":["AI Financial"],"summary":"Patch an automation rule (currently used for isEnabled toggle)","responses":{"200":{"description":"Rule patched"},"404":{"description":"Rule not found for this tenant"}}}},"/api/v3/ai/financial/collection-rules":{"get":{"tags":["AI Financial"],"summary":"List the current tenant's collection rules","responses":{"200":{"description":"List of collection rules"}}},"post":{"tags":["AI Financial"],"summary":"Create a new collection rule for the current tenant","responses":{"201":{"description":"Rule created"},"400":{"description":"Validation failed"}}}},"/api/v3/ai/financial/collection-rules/{id}":{"put":{"tags":["AI Financial"],"summary":"Replace a collection rule (partial; tenant-scoped)","responses":{"200":{"description":"Rule updated"},"404":{"description":"Rule not found for this tenant"}}},"patch":{"tags":["AI Financial"],"summary":"Patch a collection rule (currently used for isEnabled toggle)","responses":{"200":{"description":"Rule patched"},"404":{"description":"Rule not found for this tenant"}}}},"/api/v3/ai/financial/predictions":{"get":{"tags":["AI Financial"],"summary":"List payment-risk predictions (stub — always empty until ML infra lands)"}},"/api/v3/ai/financial/payment-reminders":{"get":{"tags":["AI Financial"],"summary":"List the current tenant's payment reminders","responses":{"200":{"description":"List of reminders"}}},"post":{"tags":["AI Financial"],"summary":"Create a new payment reminder for the current tenant","responses":{"201":{"description":"Reminder created"},"400":{"description":"Validation failed"}}}},"/api/v3/ai/financial/payment-reminders/{id}":{"put":{"tags":["AI Financial"],"summary":"Update an existing payment reminder (partial; tenant-scoped)","responses":{"200":{"description":"Reminder updated"},"404":{"description":"Reminder not found for this tenant"}}},"patch":{"tags":["AI Financial"],"summary":"Patch a payment reminder (currently used for isEnabled toggle)","responses":{"200":{"description":"Reminder patched"},"404":{"description":"Reminder not found for this tenant"}}}},"/api/v3/ai-models":{"get":{"tags":["AI Models"],"summary":"Get all AI model configurations for tenant","description":"Returns all AI model configurations for the current tenant, including model assignments per agent type and active strategy.","responses":{"200":{"description":"List of AI model configurations"},"401":{"description":"Unauthorized"}}},"post":{"tags":["AI Models"],"summary":"Create or update an AI model configuration","description":"Creates or updates the AI model configuration for a specific agent type. Requires admin or owner role.","responses":{"200":{"description":"Configuration saved"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}}},"/api/v3/ai-models/available":{"get":{"tags":["AI Models"],"summary":"Get list of available AI models with pricing","description":"Returns all AI models available for selection, including provider, capability tier, and per-token pricing information.","responses":{"200":{"description":"List of available AI models with pricing"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-models/strategies":{"get":{"tags":["AI Models"],"summary":"Get list of available model selection strategies","description":"Returns the available model routing strategies (e.g. cost-optimized, performance, balanced) that can be applied per tenant.","responses":{"200":{"description":"List of model selection strategies"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-models/{configId}":{"delete":{"tags":["AI Models"],"summary":"Delete an AI model configuration","description":"Permanently removes a custom AI model configuration. The tenant will fall back to default model selection. Requires admin or owner role.","parameters":[{"in":"path","name":"configId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Configuration UUID"}],"responses":{"200":{"description":"Configuration deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"},"404":{"description":"Configuration not found"}}}},"/api/v3/ai-models/select":{"post":{"tags":["AI Models"],"summary":"Select the best AI model for a given context","description":"Evaluates the current model strategy and context (agent type, task complexity) to return the optimal model identifier.","responses":{"200":{"description":"Selected model identifier"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-models/invalidate-cache":{"post":{"tags":["AI Models"],"summary":"Invalidate the AI model configuration cache","description":"Forces a cache flush for AI model configurations, ensuring all agents pick up the latest settings on the next request. Requires admin or owner role.","responses":{"200":{"description":"Cache invalidated"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}}},"/api/v3/ai/predictions/accuracy":{"get":{"tags":["AI Predictions"],"summary":"Get AI prediction accuracy statistics","description":"Returns accuracy metrics for AI predictions, optionally filtered by prediction type, to evaluate model performance over time.","parameters":[{"in":"query","name":"type","schema":{"type":"string"},"description":"Prediction type to filter by (e.g. job_duration, churn)"}],"responses":{"200":{"description":"Prediction accuracy statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/predictions/performance":{"get":{"tags":["AI Predictions"],"summary":"Get AI prediction performance summary by period","description":"Returns a rolled-up performance summary of AI predictions (accuracy, coverage, calibration) for the specified time period.","parameters":[{"in":"query","name":"period","required":true,"schema":{"type":"string","enum":["day","week","month"]},"description":"Aggregation period for performance summary"}],"responses":{"200":{"description":"Prediction performance summary"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/predictions/calibration/{type}":{"get":{"tags":["AI Predictions"],"summary":"Get prediction calibration data for a specific prediction type","description":"Returns calibration curve data showing how well predicted probabilities align with observed outcomes for a given prediction type.","parameters":[{"in":"path","name":"type","required":true,"schema":{"type":"string"},"description":"Prediction type (e.g. churn, job_duration, payment)"}],"responses":{"200":{"description":"Calibration curve data"},"401":{"description":"Unauthorized"},"404":{"description":"Calibration data not found"}}}},"/api/v3/ai/predictions/forecasts/revenue":{"get":{"tags":["AI Predictions"],"summary":"Get AI revenue forecast for a specified period","description":"Forecasts revenue for the upcoming week, month, or quarter using historical billing patterns and current pipeline data.","parameters":[{"in":"query","name":"period","required":true,"schema":{"type":"string","enum":["week","month","quarter"]},"description":"Forecast horizon"}],"responses":{"200":{"description":"Revenue forecast"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/predictions/forecasts/demand":{"get":{"tags":["AI Predictions"],"summary":"Get AI demand forecast for a service type and period","description":"Forecasts service demand (job volume) for a specific service type over the requested period, factoring in seasonal patterns and historical trends.","parameters":[{"in":"query","name":"serviceType","required":true,"schema":{"type":"string"},"description":"Service type identifier"},{"in":"query","name":"period","required":true,"schema":{"type":"string","enum":["week","month"]},"description":"Forecast horizon"}],"responses":{"200":{"description":"Demand forecast"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/predictions/forecasts/seasonal":{"get":{"tags":["AI Predictions"],"summary":"Get seasonal demand factor for a service type and date","description":"Returns the seasonal demand multiplier for a service type on a specific date, useful for capacity planning and pricing adjustments.","parameters":[{"in":"query","name":"serviceType","required":true,"schema":{"type":"string"},"description":"Service type identifier"},{"in":"query","name":"date","schema":{"type":"string","format":"date"},"description":"Date to evaluate seasonal factor for (defaults to today)"}],"responses":{"200":{"description":"Seasonal demand factor"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/predictions/forecasts/peak-periods/{serviceType}":{"get":{"tags":["AI Predictions"],"summary":"Get peak and low demand periods for a service type","description":"Returns historically identified peak demand and low demand periods for a service type, enabling proactive staffing and promotional planning.","parameters":[{"in":"path","name":"serviceType","required":true,"schema":{"type":"string"},"description":"Service type identifier"}],"responses":{"200":{"description":"Peak and low demand periods"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-predictions":{"get":{"tags":["AI Predictions"],"summary":"List AI predictions","description":"Returns a paginated list of AI predictions for the tenant, optionally filtered by type, status, and date range.","responses":{"200":{"description":"List of AI predictions"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-predictions/job-duration":{"get":{"tags":["AI Predictions"],"summary":"Get AI job duration prediction","description":"Predicts estimated job duration based on job type, technician skill, property characteristics, and historical data for scheduling accuracy.","responses":{"200":{"description":"Predicted job duration"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-predictions/customer/{customerId}":{"get":{"tags":["AI Predictions"],"summary":"Get AI predictions for a customer including churn risk and CLV","description":"Returns churn risk score, customer lifetime value estimate, and next-best-action recommendations for a specific customer.","parameters":[{"in":"path","name":"customerId","required":true,"schema":{"type":"string"},"description":"Customer UUID"}],"responses":{"200":{"description":"Customer predictions"},"401":{"description":"Unauthorized"},"404":{"description":"Customer not found"}}}},"/api/v3/ai-predictions/invoice/{invoiceId}":{"get":{"tags":["AI Predictions"],"summary":"Get AI payment prediction for an invoice","description":"Predicts payment probability and expected payment date for a specific invoice based on customer payment history and invoice characteristics.","parameters":[{"in":"path","name":"invoiceId","required":true,"schema":{"type":"string"},"description":"Invoice UUID"}],"responses":{"200":{"description":"Invoice payment prediction"},"401":{"description":"Unauthorized"},"404":{"description":"Invoice not found"}}}},"/api/v3/ai-predictions/{type}/{entityId}":{"get":{"tags":["AI Predictions"],"summary":"Get a prediction by type and entity ID","description":"Retrieves the latest AI prediction of a specific type for a given entity, including confidence score and metadata.","parameters":[{"in":"path","name":"type","required":true,"schema":{"type":"string"},"description":"Prediction type (e.g. churn, job_duration, payment)"},{"in":"path","name":"entityId","required":true,"schema":{"type":"string"},"description":"Entity UUID"}],"responses":{"200":{"description":"Prediction details"},"401":{"description":"Unauthorized"},"404":{"description":"Prediction not found"}}}},"/api/v3/ai-predictions/batch":{"post":{"tags":["AI Predictions"],"summary":"Get batch AI predictions for multiple entities","description":"Runs AI predictions for multiple entities in a single call, reducing latency when displaying predictions across a list view.","responses":{"200":{"description":"Batch prediction results"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-predictions/auto-resolve":{"post":{"tags":["AI Predictions"],"summary":"Auto-resolve old unresolved predictions","description":"Automatically marks stale unresolved predictions as expired, keeping the predictions table clean and accurate for reporting.","responses":{"200":{"description":"Auto-resolve completed with count of resolved predictions"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-predictions/record-actual":{"post":{"tags":["AI Predictions"],"summary":"Record the actual outcome for a prediction","description":"Records the ground-truth outcome for a prediction to feed back into accuracy tracking and model calibration pipelines.","responses":{"200":{"description":"Actual outcome recorded"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Prediction not found"}}}},"/api/v3/ai-settings/audit-log":{"get":{"tags":["AI Settings"],"summary":"Get AI settings audit log","description":"Returns a paginated log of all AI settings changes made for the tenant, including who made the change and what was modified.","parameters":[{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of entries to return"},{"in":"query","name":"offset","schema":{"type":"integer"},"description":"Pagination offset"}],"responses":{"200":{"description":"AI settings audit log entries"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-settings/features/with-settings":{"get":{"tags":["AI Settings"],"summary":"List AI features with their settings","description":"Returns all AI feature flags combined with their per-tenant configuration settings in a single response for the settings management UI.","responses":{"200":{"description":"AI features with settings"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-settings/features/{featureKey}/enable":{"post":{"tags":["AI Settings"],"summary":"Enable a specific AI feature","description":"Enables an AI feature flag for the tenant. Requires admin, owner, or manager role.","parameters":[{"in":"path","name":"featureKey","required":true,"schema":{"type":"string"},"description":"Feature key identifier"}],"responses":{"200":{"description":"Feature enabled"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"},"404":{"description":"Feature not found"}}}},"/api/v3/ai-settings/features/{featureKey}/disable":{"post":{"tags":["AI Settings"],"summary":"Disable a specific AI feature","description":"Disables an AI feature flag for the tenant, immediately stopping AI functionality for that feature. Requires admin, owner, or manager role.","parameters":[{"in":"path","name":"featureKey","required":true,"schema":{"type":"string"},"description":"Feature key identifier"}],"responses":{"200":{"description":"Feature disabled"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"},"404":{"description":"Feature not found"}}}},"/api/v3/ai-settings/features/{featureKey}/settings":{"patch":{"tags":["AI Settings"],"summary":"Update settings for a specific AI feature","description":"Updates the configuration settings for a specific AI feature. Requires admin, owner, or manager role.","parameters":[{"in":"path","name":"featureKey","required":true,"schema":{"type":"string"},"description":"Feature key identifier"}],"responses":{"200":{"description":"Feature settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"},"404":{"description":"Feature not found"}}}},"/api/v3/ai-settings/global":{"get":{"tags":["AI Settings"],"summary":"Get global AI settings for the tenant","description":"Returns the tenant's global AI configuration including default model, monthly budget limit, and notification preferences.","responses":{"200":{"description":"Global AI settings"},"401":{"description":"Unauthorized"}}},"patch":{"tags":["AI Settings"],"summary":"Update global AI settings for the tenant","description":"Updates the tenant's global AI configuration such as default model selection, budget limits, and notification channels.","responses":{"200":{"description":"Global settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-settings/usage":{"get":{"tags":["AI Settings"],"summary":"Get AI usage metrics","description":"Returns detailed AI token and cost usage metrics for the tenant, filterable by date range and model, for billing and quota monitoring.","responses":{"200":{"description":"AI usage metrics"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai-settings/usage/summary":{"get":{"tags":["AI Settings"],"summary":"Get AI usage summary for a date range","description":"Returns a rolled-up summary of AI token usage and costs for a specified date range, useful for monthly billing reconciliation.","parameters":[{"in":"query","name":"startDate","required":true,"schema":{"type":"string","format":"date"},"description":"Start of date range"},{"in":"query","name":"endDate","required":true,"schema":{"type":"string","format":"date"},"description":"End of date range"}],"responses":{"200":{"description":"AI usage summary"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/analytics/email":{"get":{"tags":["AI"],"summary":"Get email tracking analytics","description":"Retrieve email delivery and engagement analytics for the tenant, including sent/delivered/opened/clicked/bounced counts and calculated open/click rates.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"},"description":"Time period for email analytics"}],"responses":{"200":{"description":"Email tracking analytics metrics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analytics/feedback":{"get":{"tags":["AI"],"summary":"Get customer feedback analytics","description":"Retrieve customer feedback analytics including average ratings, NPS score, positive/negative feedback counts for the specified period.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"},"description":"Time period for feedback analytics"}],"responses":{"200":{"description":"Customer feedback analytics metrics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analytics/feedback/themes":{"get":{"tags":["AI"],"summary":"Get AI-analyzed customer feedback themes","description":"Use AI to analyze recent customer feedback and extract the most prominent positive and negative themes, with sentiment categorization.","responses":{"200":{"description":"AI-extracted feedback themes"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analytics/ai-insights":{"get":{"tags":["AI"],"summary":"Get AI-powered analytics insights","description":"Retrieve AI-generated analytics insights tailored to a specific focus area, including key metrics, trend analysis, and actionable recommendations."}},"/api/v3/ai/analytics/anomalies":{"get":{"tags":["AI"],"summary":"Detect anomalies in business metrics"}},"/api/v3/ai/analytics/forecast":{"get":{"tags":["AI"],"summary":"Get AI forecast predictions for a metric"}},"/api/v3/ai/analytics/quick-insights":{"get":{"tags":["AI"],"summary":"Get quick AI-generated business insights"}},"/api/v3/ai/analytics/performance":{"get":{"tags":["AI"],"summary":"Get performance analytics metrics","description":"Retrieve operational performance metrics for the tenant including job completion rates, average completion times, and revenue totals for the specified period.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"},"description":"Time period for performance metrics"}],"responses":{"200":{"description":"Performance analytics metrics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analytics/trends":{"get":{"tags":["AI"],"summary":"Get trend data for a business metric over time","description":"Retrieve daily time-series data for a business metric to render trend charts in dashboards and reports.","parameters":[{"in":"query","name":"metric","schema":{"type":"string","default":"jobs"},"description":"Metric to retrieve trend data for"},{"in":"query","name":"period","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"},"description":"Time period for trend data"}],"responses":{"200":{"description":"Daily trend data points for the metric"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analytics/voice":{"get":{"tags":["AI"],"summary":"Get voice call analytics statistics","description":"Retrieve voice call analytics including call volumes, average duration, outcome distribution, and booking conversion rates for the specified period.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"},"description":"Time period for voice analytics"}],"responses":{"200":{"description":"Voice call analytics statistics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analytics/voice/top-intents":{"get":{"tags":["AI"],"summary":"Get top intents from voice call analytics","description":"Retrieve the most common customer intents detected in voice calls, ranked by frequency. Use to understand what customers are calling about most often.","responses":{"200":{"description":"Top detected intents from voice calls"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/voice/insights":{"get":{"tags":["AI"],"summary":"Get AI-powered voice analytics insights","description":"Get AI-generated insights from voice call data including sentiment trends, call handling patterns, and recommendations for improving customer experience.","responses":{"200":{"description":"AI voice analytics insights"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/quick-actions":{"get":{"tags":["AI"],"summary":"Get AI quick actions for a context","description":"Retrieve context-aware AI quick actions available for a specific page or entity. Quick actions are pre-configured AI operations that can be executed with one click.","parameters":[{"in":"query","name":"page","schema":{"type":"string"},"description":"Current page context"},{"in":"query","name":"entityType","schema":{"type":"string"},"description":"Entity type for entity-specific actions"},{"in":"query","name":"entityId","schema":{"type":"string"},"description":"Entity ID for entity-specific actions"}],"responses":{"200":{"description":"Available quick actions for the context"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/quick-actions/{actionId}/execute":{"post":{"tags":["AI"],"summary":"Execute an AI quick action","description":"Execute a specific AI quick action with optional parameters. The action is applied immediately without requiring a separate approval step.","parameters":[{"in":"path","name":"actionId","required":true,"schema":{"type":"string"},"description":"Quick action identifier"}],"responses":{"200":{"description":"Quick action execution result"},"400":{"description":"Validation error — invalid action parameters"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires admin, owner, or manager role"}}}},"/api/v3/ai/bulk-action":{"post":{"tags":["AI"],"summary":"Execute a bulk AI action on multiple entities","description":"Apply an AI action to multiple entities simultaneously, such as bulk categorization, status updates, or notification sends.","responses":{"200":{"description":"Bulk action result with per-entity status"},"400":{"description":"Validation error — invalid entity IDs or action type"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/validate":{"post":{"tags":["AI"],"summary":"Validate a form with AI assistance","description":"Validate one or all fields of a form using AI, detecting semantic issues and business rule violations beyond schema validation.","responses":{"200":{"description":"AI validation results with field-level feedback"},"400":{"description":"Validation error — formId or fields missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/suggest-bulk-action":{"post":{"tags":["AI"],"summary":"Get AI suggestions for bulk actions on entities","description":"Ask AI to recommend the most appropriate bulk actions for a selected set of entities, based on their current state and history.","responses":{"200":{"description":"AI-suggested bulk actions with reasoning"},"400":{"description":"Validation error — entity IDs or entity type missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/chat":{"post":{"tags":["AI"],"summary":"Send a chat message to the AI assistant","description":"Send a message to the AI assistant with optional conversation history context. Supports both standard JSON responses and Server-Sent Events (SSE) streaming via the streamResponse flag.","responses":{"200":{"description":"AI response (JSON) or SSE stream if streamResponse is true"},"400":{"description":"Validation error — message missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/compose":{"post":{"tags":["AI"],"summary":"Generate a composed response draft using AI","description":"Generate a professional response draft based on conversation context and an operator instruction. Use for drafting replies to customers in the inbox.","responses":{"200":{"description":"AI-composed response draft"},"400":{"description":"Validation error — conversation context or instruction missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/conversation/context/{id}":{"get":{"tags":["AI"],"summary":"Get conversation context with AI smart replies","description":"Retrieve AI-enriched conversation context including message history, customer profile, and AI-suggested smart reply options.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Conversation ID"}],"responses":{"200":{"description":"Conversation context with smart replies"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Conversation not found"}}}},"/api/v3/ai/conversation/sentiment/{id}":{"get":{"tags":["AI"],"summary":"Get real-time sentiment analysis for a conversation","description":"Analyze the current sentiment of a customer conversation, tracking tone changes over the message history and flagging escalation risk.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Conversation ID"}],"responses":{"200":{"description":"Sentiment analysis with score and escalation risk"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Conversation not found"}}}},"/api/v3/ai/customer/context/{id}":{"get":{"tags":["AI"],"summary":"Get AI customer context including CLV and churn risk","description":"Retrieve AI-computed customer context including customer lifetime value, churn risk score, engagement level, and recommended next actions for the customer relationship.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Customer ID"}],"responses":{"200":{"description":"AI customer context with CLV and churn risk"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Customer not found"}}}},"/api/v3/ai/generate":{"post":{"tags":["AI"],"summary":"Generate AI content","description":"Generate structured AI content for various purposes such as email templates, job descriptions, service proposals, or invoice messages, using provided context and tone settings.","responses":{"200":{"description":"AI-generated content"},"400":{"description":"Validation error — content type or context missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/morning-briefing":{"get":{"tags":["AI"],"summary":"Get AI-generated morning briefing","description":"Retrieve a personalized AI-generated morning briefing including today's schedule summary, key metrics, action items, and business highlights. Locale-aware greeting is included.","parameters":[{"in":"query","name":"locale","schema":{"type":"string","default":"en"},"description":"Locale for greeting text (e.g. en, es)"}],"responses":{"200":{"description":"AI morning briefing with schedule, metrics, and highlights"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/predictions":{"get":{"tags":["AI"],"summary":"Get AI dashboard predictions","description":"Retrieve AI-generated predictions for the dashboard including demand forecasting, revenue projections, and resource utilization predictions.","parameters":[{"in":"query","name":"types","schema":{"type":"string"},"description":"Comma-separated prediction types (e.g. demand,revenue,resource)"},{"in":"query","name":"timeRange","schema":{"type":"string","default":"30d"},"description":"Prediction time horizon"}],"responses":{"200":{"description":"AI predictions for specified types"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/predict":{"post":{"tags":["AI"],"summary":"Get AI predictions for a specific entity","description":"Request AI predictions for a specific entity (job, customer, asset) such as failure probability, churn likelihood, or completion time.","responses":{"200":{"description":"AI predictions for the entity"},"400":{"description":"Validation error — entityType or entityId missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/analyze":{"post":{"tags":["AI"],"summary":"Run an AI analysis query","description":"Execute a natural language AI analysis query against the tenant's data. Use for ad-hoc analytics, report generation, or data exploration.","responses":{"200":{"description":"AI analysis result with insights and data"},"400":{"description":"Validation error — query missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/search":{"post":{"tags":["AI"],"summary":"Search entities using AI-powered semantic search","description":"Perform semantic search across multiple entity types using natural language queries. Returns ranked results with relevance scores across customers, jobs, properties, and more.","responses":{"200":{"description":"Ranked search results with relevance scores"},"400":{"description":"Validation error — query missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/suggestions":{"get":{"tags":["AI"],"summary":"Get AI suggestions for a page or entity context","description":"Retrieve contextual AI suggestions for the current page or a specific entity, such as next-best-actions, data improvements, or workflow recommendations.","parameters":[{"in":"query","name":"page","schema":{"type":"string"},"description":"Current page context"},{"in":"query","name":"entityType","schema":{"type":"string"},"description":"Entity type for entity-specific suggestions"},{"in":"query","name":"entityId","schema":{"type":"string"},"description":"Entity ID for entity-specific suggestions"}],"responses":{"200":{"description":"AI suggestions for the context"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/insights/{page}":{"get":{"tags":["AI"],"summary":"Get AI insights for a specific page","description":"Retrieve AI-generated insights tailored to a specific page context, with optional entity filtering and date range constraints.","parameters":[{"in":"path","name":"page","required":true,"schema":{"type":"string"},"description":"Page identifier (e.g. dashboard, jobs, customers)"},{"in":"query","name":"entityType","schema":{"type":"string"},"description":"Optional entity type filter"},{"in":"query","name":"entityId","schema":{"type":"string"},"description":"Optional entity ID filter"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start of date range"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End of date range"}],"responses":{"200":{"description":"AI insights for the page"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/insights/{id}/dismiss":{"post":{"tags":["AI"],"summary":"Dismiss an AI insight","description":"Acknowledge and dismiss an AI insight. The dismissal is stored client-side; this endpoint confirms the action server-side for tracking purposes.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Insight ID"}],"responses":{"200":{"description":"Insight dismissed"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/docs/query":{"post":{"tags":["AI Documentation"],"summary":"Query AI documentation knowledge base","description":"Submit a natural language query to the AI documentation knowledge base to retrieve relevant equipment guides, maintenance procedures, and troubleshooting information.","responses":{"200":{"description":"Relevant documentation matches with AI-synthesized answer"},"400":{"description":"Validation error — query missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/docs/guides/{equipmentType}":{"get":{"tags":["AI Documentation"],"summary":"Get equipment guide by type","description":"Retrieve the maintenance and service guide for a specific equipment type from the knowledge base.","parameters":[{"in":"path","name":"equipmentType","required":true,"schema":{"type":"string"},"description":"Equipment type identifier"}],"responses":{"200":{"description":"Equipment guide document"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"404":{"description":"Guide not found for equipment type"}}}},"/api/v3/ai/docs/feedback":{"post":{"tags":["AI Documentation"],"summary":"Submit feedback for a documentation query","description":"Rate the helpfulness of a documentation query response to improve future knowledge base quality.","responses":{"200":{"description":"Feedback submitted"},"400":{"description":"Validation error — feedback data missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/docs/history":{"get":{"tags":["AI Documentation"],"summary":"Get documentation query history","description":"Retrieve the history of documentation queries made by the tenant, useful for reviewing past lookups and identifying knowledge gaps.","parameters":[{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of history entries to return"},{"in":"query","name":"offset","schema":{"type":"integer"},"description":"Pagination offset"}],"responses":{"200":{"description":"Documentation query history"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/docs/categories":{"get":{"tags":["AI Documentation"],"summary":"Get available documentation categories","description":"Retrieve the list of available documentation categories in the knowledge base for filtering and navigation.","responses":{"200":{"description":"List of documentation categories"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/emergency/detect":{"post":{"tags":["AI Emergency Detection"],"summary":"Detect emergency using rule-based analysis","description":"Analyze a message or situation using configured rule-based emergency detection to identify urgent scenarios such as gas leaks, flooding, or safety hazards.","responses":{"200":{"description":"Emergency detection result with severity and detected conditions"},"400":{"description":"Validation error — input text missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/emergency/detect-ai":{"post":{"tags":["AI Emergency Detection"],"summary":"Detect emergency using AI-powered analysis","description":"Analyze a message using AI-powered natural language understanding to detect emergency situations. More nuanced than rule-based detection but slower and higher cost.","responses":{"200":{"description":"AI emergency detection result with confidence score"},"400":{"description":"Validation error — input text missing"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/emergency/rules":{"get":{"tags":["AI Emergency Detection"],"summary":"Get emergency detection rules configuration","description":"Retrieve the current set of emergency detection rules including keywords, patterns, and severity thresholds used for rule-based detection.","responses":{"200":{"description":"Current emergency detection rules"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}},"put":{"tags":["AI Emergency Detection"],"summary":"Update emergency detection rules configuration","description":"Update the emergency detection rule set. Requires settings:manage permission. Changes apply to all subsequent emergency detection calls.","responses":{"200":{"description":"Rules updated successfully"},"400":{"description":"Validation error — invalid rules configuration"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires settings:manage permission"}}}},"/api/v3/ai/emergency/history":{"get":{"tags":["AI Emergency Detection"],"summary":"Get emergency detection history","description":"Retrieve historical emergency detection events for the tenant, including what was detected, severity, and how it was handled.","parameters":[{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of history entries"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start date filter"}],"responses":{"200":{"description":"Emergency detection history"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/emergency/stats":{"get":{"tags":["AI Emergency Detection"],"summary":"Get emergency detection statistics","description":"Retrieve aggregate statistics for emergency detection events including counts by severity, detection method accuracy, and response time metrics.","parameters":[{"in":"query","name":"period","schema":{"type":"string"},"description":"Time period for statistics (e.g. 30d, 90d)"}],"responses":{"200":{"description":"Emergency detection statistics"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/analytics":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the analytics page header","description":"Retrieve compact AI insights for the analytics page header, including total jobs completed and revenue generated in the last 30 days.","responses":{"200":{"description":"Quick analytics insights with jobs and revenue KPIs for the last 30 days"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/migration":{"get":{"tags":["AI"],"summary":"Get quick inline migration insights","description":"Retrieve compact AI insights for the migration page header, including total record count, completed migrations, and failed records requiring attention.","responses":{"200":{"description":"Quick migration insights with total, completed, and failed record counts"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/assets":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for assets page header","description":"Retrieve compact AI insights for the assets page header, including KPIs such as total assets, assets requiring maintenance, and overall fleet health indicators.","responses":{"200":{"description":"Quick asset insights for page header display"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/inventory":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for inventory page header","description":"Retrieve compact AI insights for the inventory page header, including KPIs such as total items, low-stock alerts, and inventory valuation.","responses":{"200":{"description":"Quick inventory insights for page header display"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/assets/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for assets","description":"Retrieve detailed AI-generated insights for assets, optionally filtered by a specific asset or category, covering maintenance predictions, failure risk, and service recommendations.","parameters":[{"in":"query","name":"assetId","schema":{"type":"string","format":"uuid"},"description":"Filter insights to a specific asset UUID"},{"in":"query","name":"category","schema":{"type":"string"},"description":"Filter insights to a specific asset category"}],"responses":{"200":{"description":"Detailed asset insights with maintenance and risk analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inventory/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for inventory","description":"Retrieve detailed AI-generated insights for inventory items, optionally filtered by a specific item or category, covering reorder recommendations, demand forecasting, and stock optimization.","parameters":[{"in":"query","name":"itemId","schema":{"type":"string","format":"uuid"},"description":"Filter insights to a specific inventory item UUID"},{"in":"query","name":"category","schema":{"type":"string"},"description":"Filter insights to a specific inventory category"}],"responses":{"200":{"description":"Detailed inventory insights with reorder and demand analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/customers":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the customers page header","description":"Retrieve compact AI insights for the customers page header, including total customer count, new customers in the last 30 days, and VIP customer count.","responses":{"200":{"description":"Quick customer insights with total, new, and VIP customer counts"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/customers/insights":{"get":{"tags":["AI"],"summary":"Get AI-generated customer insights with segmentation","description":"Retrieve detailed AI-generated customer insights including churn risk alerts, growth trends, and customer segmentation (Active, At Risk, VIP) with average customer lifetime value per segment.","responses":{"200":{"description":"Customer insights with segments, churn risk alerts, and growth trends"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/estimates":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the estimates page header","description":"Retrieve compact AI insights for the estimates page header, including KPIs such as open estimates, conversion rate, and total estimate value pending approval.","responses":{"200":{"description":"Quick estimate insights for page header display"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/estimates/insights":{"get":{"tags":["AI"],"summary":"Get AI-generated estimate insights","description":"Retrieve detailed AI-generated insights for estimates, including win rate analysis, average estimate value, follow-up recommendations, and patterns in lost estimates.","responses":{"200":{"description":"Detailed estimate insights with conversion analysis and recommendations"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/feedback":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the feedback page header","description":"Retrieve compact AI insights for the feedback page header, including average customer rating, feedback submissions this week, and count of positive reviews (rating 4 or above).","responses":{"200":{"description":"Quick feedback insights with average rating, weekly volume, and positive review count"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/voice-analytics":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the voice analytics page header","description":"Retrieve compact AI insights for the voice analytics page header, including calls this week, average call duration, and total bookings generated from voice interactions.","responses":{"200":{"description":"Quick voice analytics insights with call volume, duration, and booking counts"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/invoices":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for invoices page header","description":"Retrieve compact AI insights for the invoices page header, including KPIs such as outstanding invoices, overdue amount, and collection rate.","responses":{"200":{"description":"Quick invoice insights for page header display"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/invoices/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for invoices","description":"Retrieve detailed AI-generated insights for invoices, including cash flow analysis, overdue payment patterns, payment collection recommendations, and revenue trends.","responses":{"200":{"description":"Detailed invoice insights with cash flow and payment collection analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/jobs":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the jobs page header","description":"Retrieve compact AI insights for the jobs page header, including today's scheduled job count, jobs completed this week, and count of high or urgent priority jobs.","responses":{"200":{"description":"Quick job insights with today's count, weekly completions, and high-priority job count"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/jobs/daily-insights":{"get":{"tags":["AI"],"summary":"Get AI-generated daily job insights and capacity alerts","description":"Retrieve AI-generated insights for a specific date, including unassigned jobs, heavy schedule warnings, high-priority job alerts, capacity gaps, and a summary of the day's schedule.","parameters":[{"in":"query","name":"date","schema":{"type":"string","format":"date"},"description":"Date to retrieve insights for (ISO 8601 format); defaults to today"}],"responses":{"200":{"description":"Daily job insights with capacity alerts, recommendations, and schedule summary"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/leases":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for leases page header","description":"Retrieve compact AI insights for the leases page header, including KPIs such as active leases, leases expiring soon, and renewal opportunities.","responses":{"200":{"description":"Quick lease insights for page header display"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/leases/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for leases","description":"Retrieve detailed AI-generated insights for leases, including renewal risk analysis, expiration forecasts, tenant retention recommendations, and portfolio health metrics.","responses":{"200":{"description":"Detailed lease insights with renewal risk and portfolio health analysis"},"401":{"description":"Unauthorized — missing or invalid JWT token"}}}},"/api/v3/ai/inline/properties":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for properties page header","description":"Returns a compact AI-generated summary for the Properties page header widget, including occupancy trends and maintenance alerts.","responses":{"200":{"description":"Inline AI insights for properties"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/properties/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for properties","description":"Returns detailed AI-driven insights for the PropertiesAIHeader component, including property condition analysis and recommended actions.","responses":{"200":{"description":"Detailed AI insights for properties"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/schedule":{"get":{"tags":["AI"],"summary":"Get quick inline insights for the schedule page header","description":"Returns a compact AI-generated summary for the Schedule page header widget, including today's workload and scheduling conflicts.","responses":{"200":{"description":"Inline AI insights for the schedule"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/dispatch":{"get":{"tags":["AI"],"summary":"Get quick inline insights for dispatch","description":"Returns a compact AI summary for the Dispatch header, highlighting unassigned jobs and technician availability.","responses":{"200":{"description":"Inline AI insights for dispatch"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/time-suggest":{"post":{"tags":["AI"],"summary":"Get AI-suggested time slots for scheduling","description":"Analyzes technician availability, job duration, and travel time to suggest optimal scheduling windows for a new job.","responses":{"200":{"description":"Suggested time slots"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/technician-suggest":{"post":{"tags":["AI"],"summary":"Get AI-suggested technician for a job","description":"Uses skills, location, and current workload to recommend the best-fit technician for an incoming job.","responses":{"200":{"description":"Suggested technician(s)"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/date-suggest":{"post":{"tags":["AI"],"summary":"Get AI-suggested dates for scheduling","description":"Returns optimal scheduling dates based on historical job patterns, customer preferences, and technician availability windows.","responses":{"200":{"description":"Suggested scheduling dates"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/technicians":{"get":{"tags":["AI"],"summary":"Get quick inline technician stats for the page header","description":"Returns a compact AI summary for the Technicians page header, including active count, utilization rate, and top performer highlights.","responses":{"200":{"description":"Inline technician stats"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/technicians/insights":{"get":{"tags":["AI"],"summary":"Get AI-generated technician insights","description":"Returns AI-generated performance insights for the technician workforce, including workload distribution, skill gaps, and efficiency recommendations.","responses":{"200":{"description":"AI-generated technician insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/vendors":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for vendors page header","description":"Returns a compact AI summary for the Vendors page header, including active vendor count and performance highlights.","responses":{"200":{"description":"Inline AI insights for vendors"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/territories":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for territories page header","description":"Returns a compact AI summary for the Territories page header, including coverage metrics and territory health indicators.","responses":{"200":{"description":"Inline AI insights for territories"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/vendors/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for vendors","description":"Returns detailed AI analysis for the VendorsAIHeader component, covering vendor reliability scores, cost trends, and recommended vendor actions.","responses":{"200":{"description":"Detailed AI insights for vendors"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/inline/work-orders":{"get":{"tags":["AI Inline"],"summary":"Get quick inline AI insights for work orders page header","description":"Returns a compact AI summary for the Work Orders page header, including open order count and urgency indicators.","responses":{"200":{"description":"Inline AI insights for work orders"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/work-orders/insights":{"get":{"tags":["AI Inline"],"summary":"Get detailed AI insights for work orders","description":"Returns detailed AI analysis for the WorkOrdersAIHeader component, including completion rate trends, backlog risk, and prioritization recommendations.","responses":{"200":{"description":"Detailed AI insights for work orders"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/maintenance/triage":{"post":{"tags":["AI Maintenance Triage"],"summary":"Start a new maintenance triage session","description":"Initiates an AI-guided maintenance triage conversation for a property issue, returning the first diagnostic question and session ID.","responses":{"201":{"description":"Triage session started"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/maintenance/triage/{id}/respond":{"post":{"tags":["AI Maintenance Triage"],"summary":"Respond to a triage session question","description":"Submits the user's answer to the current diagnostic question and advances the triage session to the next step.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Triage session UUID"}],"responses":{"200":{"description":"Next triage step"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/maintenance/triage/{id}/complete":{"post":{"tags":["AI Maintenance Triage"],"summary":"Complete a maintenance triage session","description":"Finalizes the triage session, generating a summary report with diagnosis, recommended actions, and urgency level.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Triage session UUID"}],"responses":{"200":{"description":"Triage completion summary"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/maintenance/triage/{id}":{"get":{"tags":["AI Maintenance Triage"],"summary":"Get a maintenance triage session by ID","description":"Retrieves the full state of a triage session including all questions, responses, and current step.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Triage session UUID"}],"responses":{"200":{"description":"Triage session details"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/analytics/kpi-summary":{"get":{"tags":["AI Panels"],"summary":"Get AI-generated KPI summary for analytics panel","description":"Returns an AI-generated narrative summary of key performance indicators including revenue, job completion rate, customer satisfaction, and technician utilization compared to the previous period.","responses":{"200":{"description":"AI KPI summary"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/analytics/trend-analysis":{"get":{"tags":["AI Panels"],"summary":"Get AI-generated trend analysis for a metric and period","description":"Returns AI-driven trend analysis identifying patterns, seasonality, and anomalies for a specific metric over a selected time period.","parameters":[{"in":"query","name":"metric","schema":{"type":"string"},"description":"Metric name to analyze (e.g. revenue, jobs)"},{"in":"query","name":"period","schema":{"type":"string"},"description":"Time period for the analysis (e.g. \"last 30 days\")"}],"responses":{"200":{"description":"AI trend analysis"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/customers/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for customers","description":"Returns customer health metrics including new acquisitions, active customer count, and churn numbers for the current month to power the Customers AI panel.","responses":{"200":{"description":"Customer insights panel data"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/director/insights":{"get":{"tags":["AI Panels"],"summary":"Get strategic AI insights for director dashboard","description":"Returns high-level strategic insights for directors, including company-wide KPI deviations, risk signals, and growth opportunities.","responses":{"200":{"description":"Director-level AI insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/dispatch/route-optimization":{"get":{"tags":["AI Panels"],"summary":"Preview the optimized job order for one technician's day","description":"Re-sequences the technician's scheduled jobs for the given date with the route optimizer and reports the travel time, distance and fuel cost the new order would save. Read-only — unlike POST /api/routing/optimize it does not persist an optimization. Answers `data: null` when no technician is selected, when route optimization is disabled for the tenant, or when fewer than two of the day's jobs have coordinates.","parameters":[{"in":"query","name":"technicianId","schema":{"type":"string","format":"uuid"},"description":"technicians.id or users.id (omit for no route)"},{"in":"query","name":"date","schema":{"type":"string","format":"date"},"description":"Day to sequence (YYYY-MM-DD, defaults to today)"}],"responses":{"200":{"description":"Route optimization preview, or null"},"400":{"description":"Unknown or malformed query parameter"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/dispatch/emergency-alerts":{"get":{"tags":["AI Panels"],"summary":"Open emergency/urgent jobs awaiting dispatch","description":"Jobs whose priority is `emergency` or `urgent` and that have not reached a terminal status, in triage order (emergencies first, then SLA breaches, then schedule).","responses":{"200":{"description":"Emergency alerts list"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/dispatch/technician-workload":{"get":{"tags":["AI Panels"],"summary":"Get AI analysis of technician workload distribution","description":"Analyzes current technician workload distribution, identifies overloaded and underutilized team members, and suggests optimal assignment rebalancing.","responses":{"200":{"description":"Technician workload analysis"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/dispatch/resource-allocation":{"get":{"tags":["AI Panels"],"summary":"Get AI resource allocation analysis for dispatch panel","description":"Analyzes resource allocation across technicians and identifies coverage gaps, recommending optimal allocation.","responses":{"200":{"description":"Resource allocation analysis"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/entity/{entityType}/{entityId}/analysis":{"get":{"tags":["AI Panels"],"summary":"Get detailed AI analysis for a specific entity","description":"Returns a deep-dive AI analysis of any entity (job, customer, technician, property), including history, performance metrics, risks, and recommendations.","parameters":[{"in":"path","name":"entityType","required":true,"schema":{"type":"string"},"description":"Entity type (e.g. job, customer, technician, property)"},{"in":"path","name":"entityId","required":true,"schema":{"type":"string"},"description":"Entity identifier"}],"responses":{"200":{"description":"AI entity analysis"},"401":{"description":"Unauthorized"},"404":{"description":"Entity not found"}}}},"/api/v3/ai/entity/{entityType}/{entityId}/recommendations":{"get":{"tags":["AI Panels"],"summary":"Get AI recommendations for a specific entity","description":"Returns actionable AI-generated recommendations tailored to a specific entity, such as next-best actions for a customer or scheduling suggestions for a job.","parameters":[{"in":"path","name":"entityType","required":true,"schema":{"type":"string"},"description":"Entity type (e.g. job, customer, technician, property)"},{"in":"path","name":"entityId","required":true,"schema":{"type":"string"},"description":"Entity identifier"}],"responses":{"200":{"description":"AI recommendations for entity"},"401":{"description":"Unauthorized"},"404":{"description":"Entity not found"}}}},"/api/v3/ai/entity/{entityType}/{entityId}/similar":{"get":{"tags":["AI Panels"],"summary":"Get similar entities using AI analysis","description":"Uses AI pattern matching to find entities of the same type with similar characteristics, history, or behavior to the specified entity.","parameters":[{"in":"path","name":"entityType","required":true,"schema":{"type":"string"},"description":"Entity type (e.g. job, customer, technician, property)"},{"in":"path","name":"entityId","required":true,"schema":{"type":"string"},"description":"Entity identifier"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Number of similar entities to return"}],"responses":{"200":{"description":"Similar entities"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/conversations/analysis":{"get":{"tags":["AI Panels"],"summary":"Get AI analysis of customer conversations","description":"Analyzes customer conversations across channels for sentiment trends, common topics, response times, and improvement areas within an optional date range.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start of analysis period"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End of analysis period"},{"in":"query","name":"channel","schema":{"type":"string"},"description":"Communication channel to filter by"}],"responses":{"200":{"description":"Conversation analysis results"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/estimates/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for estimates","description":"Returns a 30-day summary of estimate pipeline health including pending, approved, and rejected counts plus average approved value for the Estimates AI panel.","responses":{"200":{"description":"Estimates insights panel data"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/forecasts/revenue":{"get":{"tags":["AI Panels"],"summary":"Get AI revenue forecast with historical trend data","description":"Calculates a revenue forecast based on the last 12 months of invoice history and returns projected revenue, growth rate, and a 6-month historical trend.","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["month","quarter","year"],"default":"month"},"description":"Forecast period granularity"}],"responses":{"200":{"description":"Revenue forecast and historical data"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/invoices/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for invoices including overdue and pending counts","description":"Returns overdue, pending, and current-month paid invoice counts with total amounts to power the Invoices AI panel and highlight collection actions.","responses":{"200":{"description":"Invoice insights panel data"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/jobs/at-risk":{"get":{"tags":["AI Panels"],"summary":"Get AI predictions for at-risk jobs","description":"Returns AI risk predictions for jobs likely to be delayed, cancelled, or have quality issues in the next 7 days.","responses":{"200":{"description":"At-risk job predictions"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/jobs/duration-analysis":{"get":{"tags":["AI Panels"],"summary":"Get AI analysis of job duration patterns","description":"Analyzes job duration patterns to identify which job types exceed expected durations and which technicians consistently finish faster or slower.","responses":{"200":{"description":"Job duration pattern analysis"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/jobs/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for jobs with status breakdown","description":"Returns a real-time snapshot of job counts by status (scheduled, in-progress, completed today, high-priority) to power the Jobs AI insights panel.","responses":{"200":{"description":"Jobs panel insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/leases/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for leases including expiring and renewal counts","description":"Returns active leases expiring within 90 days and leases pending renewal to support proactive lease management in the Leases AI panel.","responses":{"200":{"description":"Leases panel insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/manager/insights":{"get":{"tags":["AI Panels"],"summary":"Get AI insights for manager dashboard with KPI and revenue data","description":"Returns operational KPI insights for managers including first-time fix rate, high-priority job alerts, monthly revenue progress, and average job duration metrics.","responses":{"200":{"description":"Manager dashboard AI insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/owner/deep-analysis":{"get":{"tags":["AI Panels"],"summary":"Get deep AI analysis for owner dashboard","description":"Returns a comprehensive AI-driven business analysis for owners, including financial health, operational efficiency, growth indicators, and strategic recommendations.","responses":{"200":{"description":"Deep business analysis for owner"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/properties/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for properties with status breakdown","description":"Returns property counts by status (total, active, in maintenance) to power the Properties AI panel and highlight maintenance demand.","responses":{"200":{"description":"Properties panel insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/schedule/analysis":{"get":{"tags":["AI Panels"],"summary":"Get AI analysis of the current schedule","description":"Returns an AI-generated analysis of the current scheduling state, including utilization rates, bottlenecks, and efficiency recommendations.","responses":{"200":{"description":"Schedule analysis"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/schedule/availability-gaps":{"get":{"tags":["AI Panels"],"summary":"Get AI-detected availability gaps in the schedule","description":"Identifies unbooked time windows across the team schedule that could be filled with new jobs or buffer capacity.","responses":{"200":{"description":"Availability gaps in the schedule"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/schedule/conflict-detection":{"get":{"tags":["AI Panels"],"summary":"Get AI-detected scheduling conflicts","description":"Detects and reports scheduling conflicts such as double-bookings, travel time violations, and skill mismatches in the current schedule.","responses":{"200":{"description":"Detected scheduling conflicts"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/schedule/optimal-slots":{"get":{"tags":["AI Panels"],"summary":"Get AI-recommended optimal scheduling slots","description":"Returns AI-recommended time slots for new job scheduling based on current team availability, geography, and job type requirements.","responses":{"200":{"description":"Optimal scheduling slots"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/supervisor/insights":{"get":{"tags":["AI Panels"],"summary":"Get AI insights for supervisor dashboard","description":"Returns team-level operational insights for supervisors, including team performance metrics, SLA compliance, and staff coaching recommendations.","responses":{"200":{"description":"Supervisor dashboard AI insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/tasks/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for tasks with pending and overdue counts","description":"Returns task counts by status (pending, in-progress, overdue) to power the Tasks AI panel and surface overdue action items.","responses":{"200":{"description":"Tasks panel insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/technician/insights":{"get":{"tags":["AI Panels"],"summary":"Get AI insights for individual technician dashboard","description":"Returns personalized AI insights for the currently authenticated technician, including today's job progress, completion rate, route optimization suggestions, and estimated earnings.","responses":{"200":{"description":"Individual technician AI insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/technicians/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for technicians with utilization metrics","description":"Returns the top 10 active technicians ranked by job volume over the last 30 days with average job duration, for the Technicians AI panel utilization view.","responses":{"200":{"description":"Technicians panel utilization data"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/vendors/panel":{"get":{"tags":["AI Panels"],"summary":"Get AI insights panel for vendors with job count and ratings","description":"Returns the top 10 active vendors ranked by job count with average customer ratings, for the Vendors AI panel performance overview.","responses":{"200":{"description":"Vendors panel performance data"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/photos/analyze":{"post":{"tags":["AI Photo Analysis"],"summary":"Analyze a photo using AI vision","description":"Submits a photo URL for AI vision analysis, returning detected issues, condition assessment, and maintenance recommendations for property or equipment photos.","responses":{"200":{"description":"Photo analysis results"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/photos/request/{requestId}":{"get":{"tags":["AI Photo Analysis"],"summary":"Get all photo analyses for a specific request","description":"Returns all photo analyses associated with a service request, allowing technicians to review AI findings for each submitted photo.","parameters":[{"in":"path","name":"requestId","required":true,"schema":{"type":"string"},"description":"Service request identifier"}],"responses":{"200":{"description":"Photo analyses for the request"},"401":{"description":"Unauthorized"},"404":{"description":"Request not found"}}}},"/api/v3/ai/photos/config":{"get":{"tags":["AI Photo Analysis"],"summary":"Get photo analysis configuration","description":"Returns the current configuration for AI photo analysis including enabled models, confidence thresholds, and analysis categories.","responses":{"200":{"description":"Photo analysis configuration"},"401":{"description":"Unauthorized"}}},"put":{"tags":["AI Photo Analysis"],"summary":"Update photo analysis configuration","description":"Updates photo analysis settings such as enabled models, confidence thresholds, and analysis categories. Requires settings management permission.","responses":{"200":{"description":"Configuration updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}}},"/api/v3/ai/photos/history":{"get":{"tags":["AI Photo Analysis"],"summary":"Get photo analysis history","description":"Returns a paginated history of all photo analyses performed for the tenant, useful for auditing and reviewing past AI assessments.","responses":{"200":{"description":"Photo analysis history"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/photos/{id}":{"get":{"tags":["AI Photo Analysis"],"summary":"Get a specific photo analysis by ID","description":"Retrieves the full AI analysis result for a specific photo, including detected conditions, confidence scores, and recommended actions.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Photo analysis UUID"}],"responses":{"200":{"description":"Photo analysis details"},"401":{"description":"Unauthorized"},"404":{"description":"Analysis not found"}}}},"/api/v3/ai/troubleshoot/start":{"post":{"tags":["AI Troubleshooting"],"summary":"Start a new AI-guided troubleshooting session","description":"Initiates an AI-guided troubleshooting session for a specific issue, returning the first diagnostic step and session ID for tracking progress.","responses":{"201":{"description":"Troubleshooting session started"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/troubleshoot/{sessionId}/step":{"post":{"tags":["AI Troubleshooting"],"summary":"Complete a step in a troubleshooting session","description":"Records step completion and advances the session to the next diagnostic action, potentially branching based on observed results.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string"},"description":"Troubleshooting session UUID"}],"responses":{"200":{"description":"Next step in the session"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/troubleshoot/{sessionId}/photo":{"post":{"tags":["AI Troubleshooting"],"summary":"Submit a photo for AI analysis in a troubleshooting session","description":"Submits a photo taken during the troubleshooting process for AI vision analysis, incorporating the results into the diagnostic flow.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string"},"description":"Troubleshooting session UUID"}],"responses":{"200":{"description":"Photo analyzed and session updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/troubleshoot/{sessionId}/resolve":{"post":{"tags":["AI Troubleshooting"],"summary":"Resolve a troubleshooting session","description":"Marks the troubleshooting session as resolved with the applied fix, capturing resolution data for future training and reporting.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string"},"description":"Troubleshooting session UUID"}],"responses":{"200":{"description":"Session resolved"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/troubleshoot/{sessionId}/escalate":{"post":{"tags":["AI Troubleshooting"],"summary":"Escalate a troubleshooting session to human support","description":"Escalates the session beyond AI guidance to a human support specialist, preserving all diagnostic history for handoff context.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string"},"description":"Troubleshooting session UUID"}],"responses":{"200":{"description":"Session escalated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/troubleshoot/{sessionId}":{"get":{"tags":["AI Troubleshooting"],"summary":"Get a troubleshooting session by ID","description":"Retrieves the full troubleshooting session including all completed steps, photo analyses, and current status.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string"},"description":"Troubleshooting session UUID"}],"responses":{"200":{"description":"Troubleshooting session details"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/v3/ai/troubleshoot/flows":{"get":{"tags":["AI Troubleshooting"],"summary":"List available troubleshooting flows","description":"Returns all defined troubleshooting decision-tree flows available to initiate new sessions, organized by equipment or issue category.","responses":{"200":{"description":"Available troubleshooting flows"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/troubleshoot/metrics":{"get":{"tags":["AI Troubleshooting"],"summary":"Get troubleshooting session metrics","description":"Returns aggregate metrics for troubleshooting sessions including resolution rate, average time to resolve, escalation rate, and most common issue types.","responses":{"200":{"description":"Troubleshooting metrics"},"401":{"description":"Unauthorized"}}}},"/api/v3/ai/usage":{"get":{"tags":["AI"],"summary":"Get AI usage metrics for current month (alias of /billing/ai-automation-usage)","description":"Returns AI credits consumed, voice minutes used, automation run counts,\nand estimated cost savings for the current billing period. Backed by\nthe same `AIAutomationUsageService` as `/billing/ai-automation-usage`.","responses":{"200":{"description":"AI usage metrics"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/correlations":{"get":{"tags":["Analytics"],"summary":"Get metric correlation matrix and insights","description":"Calculates pairwise correlations between key business metrics over the selected time range and surfaces the strongest positive and negative relationships.","parameters":[{"in":"query","name":"range","schema":{"type":"string","enum":["30d","90d","180d"],"default":"90d"},"description":"Time range for correlation analysis"}],"responses":{"200":{"description":"Correlation matrix and top insights"},"400":{"description":"Invalid query parameters"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/locations":{"get":{"tags":["Analytics"],"summary":"Get enterprise locations overview","description":"Returns an overview of all enterprise locations including job volume, revenue, and performance scores for multi-location business dashboards.","responses":{"200":{"description":"Enterprise locations overview"},"401":{"description":"Unauthorized"},"403":{"description":"Upgrade required (non-enterprise tier)"}}}},"/api/v3/analytics/alerts":{"get":{"tags":["Analytics"],"summary":"Get enterprise system alerts","description":"Returns active system-level alerts for the enterprise dashboard, including threshold breaches, anomalies, and operational warnings.","responses":{"200":{"description":"Enterprise system alerts"},"401":{"description":"Unauthorized"},"403":{"description":"Upgrade required (non-enterprise tier)"}}}},"/api/v3/analytics/revenue-trend":{"get":{"tags":["Analytics"],"summary":"Get monthly revenue trend data","description":"Returns month-by-month revenue figures for the enterprise trend chart, enabling visual tracking of revenue growth and seasonality.","responses":{"200":{"description":"Monthly revenue trend data"},"401":{"description":"Unauthorized"},"403":{"description":"Upgrade required (non-enterprise tier)"}}}},"/api/v3/analytics/gsc/performance":{"get":{"tags":["Analytics"],"summary":"Get Google Search Console performance data","description":"Proxies Google Search Console performance metrics (clicks, impressions, CTR, position) for the connected property over a specified date range.","responses":{"200":{"description":"GSC performance data"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/gsc/pages":{"get":{"tags":["Analytics"],"summary":"Get top pages from Google Search Console","description":"Returns the top landing pages ranked by clicks from Google Search Console, used for SEO content analysis.","responses":{"200":{"description":"Top pages from GSC"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/gsc/queries":{"get":{"tags":["Analytics"],"summary":"Get top search queries from Google Search Console","description":"Returns the top search queries driving impressions and clicks from Google Search Console, useful for keyword and content strategy.","responses":{"200":{"description":"Top search queries from GSC"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/query":{"post":{"tags":["Analytics"],"summary":"Natural Language Query - ask questions in plain language","description":"Process natural language questions about business data and receive structured answers.\nSupports Russian and English. Restricted to admin/owner/manager roles.\nExamples: \"Сколько заработали сегодня?\", \"Какой техник лучший?\"","responses":{"200":{"description":"Query result with answer and supporting data"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}}},"/api/v3/analytics/report":{"get":{"tags":["Analytics"],"summary":"Get or generate analytics report","description":"Returns a cached analytics report for the requested period or triggers generation if no up-to-date report exists. Reports include revenue, jobs, technician performance, and customer metrics.","responses":{"200":{"description":"Analytics report data"},"400":{"description":"Invalid query parameters"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/status":{"get":{"tags":["Analytics"],"summary":"Get SEO integration status (Ahrefs and Core Web Vitals)","description":"Returns the connection status for all SEO integrations (Ahrefs and Core Web Vitals), indicating whether they are active and last sync time.","responses":{"200":{"description":"SEO integration status"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/domain-metrics":{"get":{"tags":["Analytics"],"summary":"Get Ahrefs domain metrics","description":"Returns Ahrefs domain authority metrics including Domain Rating, backlink count, and organic keyword estimates for the connected domain.","responses":{"200":{"description":"Ahrefs domain metrics"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/backlinks":{"get":{"tags":["Analytics"],"summary":"Get Ahrefs backlinks data","description":"Returns a paginated list of backlinks to the connected domain from Ahrefs, including referring page URL, anchor text, and domain rating.","responses":{"200":{"description":"Backlinks data"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/referring-domains":{"get":{"tags":["Analytics"],"summary":"Get Ahrefs referring domains data","description":"Returns the list of referring domains linking to the connected site from Ahrefs, showing domain authority and number of backlinks per domain.","responses":{"200":{"description":"Referring domains data"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/cwv":{"get":{"tags":["Analytics"],"summary":"Get Core Web Vitals data","description":"Returns Core Web Vitals measurements (LCP, FID, CLS) for the connected site, grouped by page and time period.","responses":{"200":{"description":"Core Web Vitals data"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/pagespeed":{"get":{"tags":["Analytics"],"summary":"Get PageSpeed Insights data","description":"Returns Google PageSpeed Insights scores and recommendations for the configured URL, including mobile and desktop performance scores.","responses":{"200":{"description":"PageSpeed Insights data"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/connect-ahrefs":{"post":{"tags":["Analytics"],"summary":"Connect Ahrefs API integration","description":"Stores Ahrefs API credentials and validates the connection, enabling backlink and domain metrics data to flow into the SEO analytics panel.","responses":{"200":{"description":"Ahrefs connected successfully"},"400":{"description":"Invalid credentials"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/connect-cwv":{"post":{"tags":["Analytics"],"summary":"Connect Core Web Vitals integration","description":"Configures the Core Web Vitals data source, enabling real user monitoring metrics to appear in the SEO analytics panel.","responses":{"200":{"description":"CWV integration connected"},"400":{"description":"Invalid configuration"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/disconnect-ahrefs":{"delete":{"tags":["Analytics"],"summary":"Disconnect Ahrefs API integration","description":"Removes Ahrefs API credentials and disables the integration for the tenant.","responses":{"200":{"description":"Ahrefs disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/seo/disconnect-cwv":{"delete":{"tags":["Analytics"],"summary":"Disconnect Core Web Vitals integration","description":"Removes Core Web Vitals configuration and disables the integration for the tenant.","responses":{"200":{"description":"CWV integration disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/settings":{"get":{"tags":["Analytics"],"summary":"Get analytics settings","description":"Returns the tenant's analytics configuration including auto-update schedule, default reporting period, and report generation preferences.","responses":{"200":{"description":"Analytics settings"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Analytics"],"summary":"Update analytics settings","description":"Updates the tenant's analytics configuration such as auto-update schedule and default period. Requires admin, owner, or manager role.","responses":{"200":{"description":"Settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}}},"/api/v3/analytics/time-series":{"get":{"tags":["Analytics"],"summary":"Get time series decomposition and forecast","description":"Decomposes a business metric's time series into trend, seasonal, and residual components, and provides a forward forecast with confidence intervals for capacity and revenue planning.","parameters":[{"name":"metric","in":"query","required":false,"schema":{"type":"string","enum":["revenue","jobs","customers","work_orders","leads"],"default":"revenue"},"description":"The business metric to analyze"},{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["daily","weekly","monthly"],"default":"daily"},"description":"The time granularity for aggregation"},{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":730},"description":"Window size in days for the date-preset buttons (7/30/90/365). When omitted, the handler falls back to a period-based default (180 for daily, ~104 weeks for weekly, ~24 months for monthly)."}],"responses":{"200":{"description":"Time series data with decomposition and forecast"},"400":{"description":"Invalid query parameters"},"401":{"description":"Unauthorized"}}}},"/api/v3/analytics/usage":{"get":{"tags":["Analytics"],"summary":"Get token usage statistics","description":"Returns AI token consumption statistics for the tenant including total tokens used, estimated cost, generation count, and recent generation history.","responses":{"200":{"description":"Token usage statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/api-keys":{"get":{"tags":["API Keys"],"summary":"List all active API keys for the tenant","description":"Returns all active API keys for the tenant with metadata (name, prefix, permissions, expiry) but without the secret hash. Requires admin or owner role.","responses":{"200":{"description":"List of active API keys"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}},"post":{"tags":["API Keys"],"summary":"Create a new API key (secret returned only once)","description":"Generates a new API key with an optional permission set and expiry. The full secret is returned only in this response — store it securely. Requires admin or owner role.","responses":{"201":{"description":"API key created with full secret"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"}}}},"/api/v3/api-keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Revoke an API key","description":"Soft-deletes (revokes) an API key immediately. All subsequent requests using this key will be rejected. Requires admin or owner role.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"API key UUID"}],"responses":{"200":{"description":"API key revoked"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions"},"404":{"description":"API key not found"}}}},"/api/v3/auth/accept-invite":{"post":{"tags":["Auth"],"summary":"Accept an invitation and set password to join tenant (legacy path)","description":"Validates the invite token, sets the user's password, and returns access and refresh tokens — auto-logging the user in after accepting.","responses":{"200":{"description":"Invitation accepted, user logged in"},"400":{"description":"Invalid or expired invite token"}}}},"/api/v3/auth/invites/accept":{"post":{"tags":["Auth"],"summary":"Accept an invitation and set password to join tenant","responses":{"200":{"description":"Invitation accepted, user logged in"},"400":{"description":"Invalid or expired invite token"}}}},"/api/v3/auth/invites/validate":{"get":{"tags":["Auth"],"summary":"Validate an invite token without consuming it","description":"Returns whether the invite token is still valid (exists, unexpired, status=invited). Used by InviteAcceptPage on mount to surface the invalid/expired card immediately.","parameters":[{"in":"query","name":"token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ success: true, data: { valid: boolean } }"}}}},"/api/auth/verify-email":{"post":{"tags":["Auth"],"summary":"Confirm a user's email using the token from the verification email","description":"Looks up the SHA-256 hash of the supplied token, marks the row used, flips users.email_verified to true. Single-use, 24-hour TTL. Public endpoint — no auth required since the user clicks the link before logging in.","responses":{"200":{"description":"Email verified"},"400":{"description":"Validation error (missing token)"},"401":{"description":"Token is invalid, used, or expired"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/resend-verification":{"post":{"tags":["Auth"],"summary":"Re-issue a verification email for an unverified address","description":"Always returns 200 — does not disclose whether the email exists or is already verified. Internally only sends if a matching unverified user is found.","responses":{"200":{"description":"Always success (enumeration-safe)"},"400":{"description":"Validation error"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/login":{"post":{"tags":["Auth"],"summary":"Login with email and password","description":"Authenticates a user with email and password credentials, returning an access token and setting a refresh token cookie. Rate limited. Supports reCAPTCHA verification.","responses":{"200":{"description":"Login successful, tokens returned"},"400":{"description":"Validation error"},"401":{"description":"Invalid credentials"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/login/2fa":{"post":{"tags":["Auth"],"summary":"Complete two-factor authentication","description":"Validates a TOTP or SMS code after the initial login step, completing the 2FA challenge and returning full session tokens.","responses":{"200":{"description":"2FA completed, tokens returned"},"400":{"description":"Invalid or expired 2FA code"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/register":{"post":{"tags":["Auth"],"summary":"Register a new user within an existing tenant","description":"Creates a new user account within an existing tenant (invite-based flow). Use /auth/signup for self-service tenant creation.","responses":{"201":{"description":"User registered"},"400":{"description":"Validation error or email already exists"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/validate":{"post":{"tags":["Auth"],"summary":"Validate an auth token","description":"Verifies an access token is valid, unexpired, and belongs to an active user. Used by services to verify incoming JWTs.","responses":{"200":{"description":"Token is valid"},"401":{"description":"Token invalid or expired"}}}},"/api/auth/recaptcha/config":{"get":{"tags":["Auth"],"summary":"Get reCAPTCHA configuration","description":"Returns the public reCAPTCHA site key and whether verification is required for the current environment, used to initialize the frontend reCAPTCHA widget.","responses":{"200":{"description":"reCAPTCHA configuration"}}}},"/api/auth/stripe/config":{"get":{"tags":["Auth"],"summary":"Get the environment's Stripe publishable key","description":"Returns the runtime Stripe publishable key (pk_test on staging, pk_live on prod) so the shared web image can load the correct key per environment without baking it in at build time. The publishable key is non-secret.","responses":{"200":{"description":"Stripe publishable key for the current environment"}}}},"/api/auth/oauth/providers":{"get":{"tags":["Auth"],"summary":"List available OAuth providers","description":"Returns the list of configured OAuth providers (e.g. Google, Microsoft) available for the current tenant for the login page.","responses":{"200":{"description":"Available OAuth providers"}}}},"/api/auth/oauth/{provider}":{"get":{"tags":["Auth"],"summary":"Initiate OAuth redirect for a provider","description":"Redirects the user to the OAuth provider's authorization page to begin the OAuth2 flow.","parameters":[{"in":"path","name":"provider","required":true,"schema":{"type":"string"},"description":"OAuth provider name (e.g. google, microsoft)"}],"responses":{"302":{"description":"Redirect to OAuth provider"},"400":{"description":"Invalid provider"}}}},"/api/auth/oauth/{provider}/callback":{"get":{"tags":["Auth"],"summary":"Handle OAuth callback from provider","description":"Receives the OAuth authorization code from the provider, exchanges it for tokens, and logs the user in or creates a new account.","parameters":[{"in":"path","name":"provider","required":true,"schema":{"type":"string"},"description":"OAuth provider name"}],"responses":{"302":{"description":"Redirect to frontend with session tokens"},"400":{"description":"OAuth error or state mismatch"}}}},"/api/auth/oauth/{provider}/link":{"post":{"tags":["Auth"],"summary":"Link OAuth account to existing user","description":"Links an OAuth provider account to the currently authenticated user, enabling future logins via that provider without a password.","parameters":[{"in":"path","name":"provider","required":true,"schema":{"type":"string"},"description":"OAuth provider name"}],"responses":{"200":{"description":"OAuth account linked"},"400":{"description":"Validation error or account already linked"},"401":{"description":"Unauthorized"}}}},"/api/auth/oauth/{provider}/unlink":{"delete":{"tags":["Auth"],"summary":"Unlink OAuth account from user","description":"Removes the OAuth provider link from the authenticated user. The user must have another login method before unlinking.","parameters":[{"in":"path","name":"provider","required":true,"schema":{"type":"string"},"description":"OAuth provider name"}],"responses":{"200":{"description":"OAuth account unlinked"},"401":{"description":"Unauthorized"},"404":{"description":"OAuth link not found"}}}},"/api/auth/change-password":{"post":{"tags":["Auth"],"summary":"Change authenticated user's password","description":"Changes the password for the currently authenticated user. Requires current password verification. Rate limited to prevent brute-force of the current password.","responses":{"200":{"description":"Password changed successfully"},"400":{"description":"Current password incorrect or validation error"},"401":{"description":"Unauthorized"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password reset email","description":"Sends a password reset link to the provided email address if an account exists. Rate limited and reCAPTCHA protected to prevent abuse.","responses":{"200":{"description":"Reset email sent (always 200 to prevent email enumeration)"},"400":{"description":"Validation error"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Complete a password reset using a token from the forgot-password email","description":"Verifies the reset token (single-use, 60-min TTL), updates the user's password, and bumps token version so existing sessions are invalidated. Rate limited to deter token brute-forcing.","responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Validation error or weak password"},"401":{"description":"Token is invalid, used, or expired"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/validate-reset-token":{"post":{"tags":["Auth"],"summary":"Pre-validate a password reset token","description":"Read-only check that the supplied reset token is currently\nvalid (issued, not used, not expired). Used by\n`useResetPasswordPage` on mount so the UI can flip straight\nto the InvalidTokenCard for bad tokens instead of letting\nthe user fill in a password that will fail.","responses":{"200":{"description":"{ valid: boolean }"},"400":{"description":"Validation error (missing token in body)"},"429":{"description":"Rate limit exceeded"}}}},"/api/v3/auth/refresh":{"post":{"tags":["Auth"],"summary":"Refresh access token using refresh token","description":"Exchanges a valid refresh token (from cookie or body) for a new access token and refresh token pair, extending the session.","responses":{"200":{"description":"New tokens returned"},"400":{"description":"Refresh token missing"},"401":{"description":"Refresh token invalid or expired"}}}},"/api/v3/auth/logout":{"post":{"tags":["Auth"],"summary":"Logout current session","description":"Invalidates the current session tokens and clears the refresh token cookie, logging the user out from the current device.","responses":{"200":{"description":"Logged out successfully"},"401":{"description":"Unauthorized"}}}},"/api/v3/auth/logout-all":{"post":{"tags":["Auth"],"summary":"Logout from all devices","description":"Revokes all active refresh tokens for the user, forcing re-authentication on every device. Use for security incidents or suspected compromise.","responses":{"200":{"description":"Logged out from all devices"},"401":{"description":"Unauthorized"}}}},"/api/v3/auth/me":{"get":{"tags":["Auth"],"summary":"Get current authenticated user profile","description":"Returns the authenticated user's profile data including roles, tenant association, and preferences. Use on app load to hydrate the user context.","responses":{"200":{"description":"Authenticated user profile"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"503":{"description":"Service unavailable (database error)"}}}},"/api/v3/auth/sessions":{"get":{"tags":["Auth"],"summary":"List the current user's active device sessions","description":"Returns the user's active (non-revoked) device sessions for the active-devices management screen. The session the request itself rides on is flagged `current`.","responses":{"200":{"description":"Active sessions for the authenticated user"},"401":{"description":"Unauthorized"}}}},"/api/v3/auth/sessions/{sessionId}":{"delete":{"tags":["Auth"],"summary":"Revoke one device session","description":"Signs the user out of a single device by revoking that session. Owner-scoped — a session id that isn't the caller's is a no-op. Does not affect the user's other devices. If the caller revokes the device it's currently on, its refresh cookie is also cleared.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session revoked (or already inactive)"},"401":{"description":"Unauthorized"}}}},"/api/v3/auth/signup":{"post":{"tags":["Auth"],"summary":"Self-service signup — creates tenant and user in one step","description":"Creates a new tenant and owner user in a single transactional step. Returns tokens for immediate login. Rate limited and reCAPTCHA protected.","responses":{"201":{"description":"Tenant and user created, tokens returned"},"400":{"description":"Validation error or email already exists"},"429":{"description":"Rate limit exceeded"}}}},"/api/v3/auth/sso/lookup":{"get":{"tags":["Authentication"],"summary":"SSO Lookup","description":"Checks whether SSO is configured for the email's domain, returning the provider type and redirect URL if enabled. Used to determine login flow on the login page.","parameters":[{"in":"query","name":"email","required":true,"schema":{"type":"string"},"description":"Email address to look up SSO configuration for"}],"responses":{"200":{"description":"SSO configuration status for the domain"},"400":{"description":"Validation error"}}}},"/api/v3/auth/sso/oidc/callback":{"get":{"tags":["Authentication"],"summary":"OIDC Callback","description":"Receives the OIDC authorization code from the Identity Provider, validates the state parameter, exchanges the code for tokens, and redirects the user to the frontend with a session.","parameters":[{"in":"query","name":"code","schema":{"type":"string"},"description":"Authorization code from the IdP"},{"in":"query","name":"state","schema":{"type":"string"},"description":"State parameter for CSRF protection"},{"in":"query","name":"error","schema":{"type":"string"},"description":"Error code from IdP (if authentication failed)"}],"responses":{"302":{"description":"Redirect to frontend with tokens or error message"}}}},"/api/auth/sso/saml/acs":{"post":{"tags":["Auth"],"summary":"SAML Assertion Consumer Service endpoint","description":"Receives and validates SAML assertions from the Identity Provider after successful authentication. Processes the assertion and establishes a user session.","responses":{"302":{"description":"Redirect to frontend with session tokens"},"400":{"description":"Invalid or malformed SAML assertion"}}}},"/api/auth/sso/saml/initiate":{"get":{"tags":["Auth"],"summary":"Initiate SAML authentication flow","description":"Generates a SAML AuthnRequest and redirects the user to the configured Identity Provider to begin the SAML SSO login flow.","responses":{"302":{"description":"Redirect to SAML Identity Provider"},"400":{"description":"SAML not configured for the domain"}}}},"/api/v3/batch":{"post":{"tags":["Batch"],"summary":"Execute multiple API requests in a single batch call","description":"Executes up to the configured maximum number of API requests in parallel or sequentially within a single HTTP call, reducing round-trips for dashboard initialization.","responses":{"200":{"description":"Batch results with per-request success/failure and aggregate stats"},"400":{"description":"Validation error or blocked path"},"401":{"description":"Unauthorized"}}}},"/api/v3/batch/limits":{"get":{"tags":["Batch"],"summary":"Get batch API limits for client-side validation","description":"Returns the configured batch API constraints (max requests, timeout, max response size, blocked paths) so clients can validate requests before submitting.","responses":{"200":{"description":"Batch API limits configuration"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/ai-automation-usage":{"get":{"tags":["Billing"],"summary":"Get AI & automation usage metrics for current month","description":"Returns AI credits consumed, voice minutes used, automation run counts, and estimated cost savings for the current billing period.","responses":{"200":{"description":"AI automation usage metrics"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/cancel":{"post":{"tags":["Billing"],"summary":"Cancel subscription at end of billing period","description":"Schedules the tenant's subscription for cancellation at the end of the current billing period, allowing continued access until then.","responses":{"200":{"description":"Cancellation scheduled"},"400":{"description":"Validation error or subscription not active"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/reactivate":{"post":{"tags":["Billing"],"summary":"Reactivate a cancelled subscription before period ends","description":"Cancels a pending subscription cancellation, restoring the subscription to active status before the billing period ends.","responses":{"200":{"description":"Subscription reactivated"},"400":{"description":"Validation error or no pending cancellation"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/resume":{"post":{"tags":["Billing"],"summary":"Resume a cancelled subscription before period ends (alias of /reactivate)","description":"Both\n`/billing/reactivate` and `/billing/resume` map to the same\nhandler so frontend and operator scripts can use either name.","responses":{"200":{"description":"Subscription reactivated"},"400":{"description":"Validation error or no pending cancellation"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/connect/status":{"get":{"tags":["Billing"],"summary":"Get Stripe Connect onboarding status","description":"Returns the Stripe Connect Express account onboarding status, including whether payouts are enabled, for the authenticated tenant.","responses":{"200":{"description":"Connect account status and payouts_enabled flag"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/connect/onboard":{"post":{"tags":["Billing"],"summary":"Start Stripe Connect Express onboarding","description":"Creates or retrieves a Stripe Connect Express account for the tenant and returns an onboarding link to complete payment gateway setup.","responses":{"200":{"description":"Stripe Connect onboarding URL"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/connect/dashboard":{"get":{"tags":["Billing"],"summary":"Get Stripe Express Dashboard login link","description":"Generates a short-lived Stripe Express Dashboard login link so the tenant can view their payout history and account details directly in Stripe.","responses":{"200":{"description":"Express Dashboard login URL"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/connect/balance":{"get":{"tags":["Billing"],"summary":"Get Stripe Connect account balance","description":"Returns the available and pending payout balances for the tenant's Stripe Connect Express account.","responses":{"200":{"description":"Connect account available and pending balances"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/connect/payouts":{"get":{"tags":["Billing"],"summary":"List recent Stripe Connect payouts","description":"Returns the tenant's recent bank payouts (synced from the payout.paid / payout.failed Connect webhooks), most recent first. Empty when the tenant has no connected account or no payouts yet.","responses":{"200":{"description":"Recent payout records (amount, status, arrival date, failure reason)"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/connect/settings":{"patch":{"tags":["Billing"],"summary":"Update customer payment settings","description":"Toggle whether the customer pays the card processing fee (convenience-fee surcharge) so the contractor nets the full invoice amount.","responses":{"200":{"description":"Updated customer payment settings"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/current":{"get":{"tags":["Billing"],"summary":"Get current subscription tier and status","description":"Returns the current tenant's subscription tier, status, and days remaining. Available to all authenticated users for tier checks in the UI.","responses":{"200":{"description":"Current subscription tier and status"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/invoices":{"get":{"tags":["Billing"],"summary":"List billing invoices","description":"Retrieve the billing invoice history for the current tenant from Stripe, ordered by date descending.","responses":{"200":{"description":"List of billing invoices"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/invoices/{id}":{"get":{"tags":["Billing"],"summary":"Get invoice details by ID","description":"Retrieve full details of a specific billing invoice including line items and payment status.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Invoice ID"}],"responses":{"200":{"description":"Invoice details"},"401":{"description":"Unauthorized"},"404":{"description":"Invoice not found"}}}},"/api/v3/billing/payment-methods":{"get":{"tags":["Billing"],"summary":"List payment methods on file","description":"Retrieve all saved payment methods for the current tenant from Stripe, including card details and default status.","responses":{"200":{"description":"List of payment methods"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Billing"],"summary":"Attach a new payment method","description":"Attach a Stripe payment method to the tenant's customer account using a payment method ID.","responses":{"201":{"description":"Payment method attached"},"400":{"description":"Invalid payment method ID"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/payment-methods/setup-intent":{"post":{"tags":["Billing"],"summary":"Create a Setup Intent for the tenant's Stripe customer","description":"Creates a Stripe SetupIntent tied to the authenticated tenant's existing Stripe customer, for securely adding a new payment method from the billing page.","responses":{"200":{"description":"SetupIntent client secret returned"},"400":{"description":"No Stripe customer configured"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/payment-methods/{id}":{"delete":{"tags":["Billing"],"summary":"Detach a payment method","description":"Remove a saved payment method from the tenant's Stripe account.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Stripe payment method ID"}],"responses":{"200":{"description":"Payment method detached"},"401":{"description":"Unauthorized"},"404":{"description":"Payment method not found"}}}},"/api/v3/billing/payment-methods/{id}/default":{"put":{"tags":["Billing"],"summary":"Set a payment method as default","description":"Set the specified payment method as the default for future charges on the tenant's Stripe account.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Stripe payment method ID"}],"responses":{"200":{"description":"Default payment method updated"},"401":{"description":"Unauthorized"},"404":{"description":"Payment method not found"}}}},"/api/v3/billing/plans":{"get":{"tags":["Billing"],"summary":"List available subscription plans","description":"Retrieve available subscription plans from Stripe with pricing, features, and seat limits. Falls back to default plans if Stripe is unavailable.","responses":{"200":{"description":"List of available subscription plans"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/seats":{"get":{"tags":["Billing"],"summary":"Get seat-based billing information","description":"Returns current seat count, seat limit, and per-seat pricing for the tenant's subscription plan.","responses":{"200":{"description":"Seat billing information"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Billing"],"summary":"Purchase seats (deprecated - returns 410 Gone)","description":"Deprecated endpoint. Seats are now auto-managed by EmployeeBillingService. Always returns 410 Gone.","responses":{"401":{"description":"Unauthorized"},"410":{"description":"Endpoint deprecated - seats are auto-managed"}}}},"/api/v3/billing/employee-seats":{"get":{"tags":["Billing"],"summary":"Get employee seat assignments and billing info","description":"Returns per-employee seat assignments, billable status, and seat utilization for the current billing period.","responses":{"200":{"description":"Employee seat assignment details"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/technician-seats":{"get":{"tags":["Billing"],"summary":"Get technician seat assignments (alias for employee-seats)","description":"Backward compatibility alias for /employee-seats. Returns the same seat assignment data.","responses":{"200":{"description":"Technician seat assignment details"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/create-setup-intent":{"post":{"tags":["Billing"],"summary":"Create a Stripe SetupIntent for the signup flow","description":"Creates a Stripe SetupIntent used to securely collect and save payment method details during tenant signup. Rate limited to prevent card testing abuse.","responses":{"200":{"description":"SetupIntent client secret returned"},"400":{"description":"Invalid request body"},"429":{"description":"Too many requests"}}}},"/api/v3/billing/confirm-setup":{"post":{"tags":["Billing"],"summary":"Confirm a Stripe SetupIntent and attach payment method","description":"Confirms a SetupIntent after 3DS authentication and attaches the resulting payment method to the tenant's Stripe account.","responses":{"200":{"description":"Setup confirmed and payment method attached"},"400":{"description":"Invalid or expired SetupIntent"},"429":{"description":"Too many requests"}}}},"/api/v3/billing/create-signup-checkout":{"post":{"tags":["Billing"],"summary":"Start a payment-first signup via Stripe Checkout","description":"Validates the full signup payload, creates a Stripe customer + a hosted Checkout Session (subscription mode) for the chosen tier, parks the payload (bcrypt-hashed password) in pending_signups, and returns the Checkout URL. The tenant is provisioned only after Stripe confirms payment, on the checkout.session.completed webhook.","responses":{"200":{"description":"Checkout URL returned"},"409":{"description":"Email already registered or a checkout is already in progress"},"422":{"description":"Weak password"},"503":{"description":"Payment system or plan price not configured"}}}},"/api/v3/billing/signup-finalize":{"post":{"tags":["Billing"],"summary":"Finalize a payment-first signup — log the owner in","description":"Polled by the finalize page after Stripe redirects back with the Checkout Session id. Returns status 'processing' while the provisioning webhook is in flight, then 'ready' with login tokens once the tenant exists.","responses":{"200":{"description":"Tenant provisioned — tokens returned"},"202":{"description":"Paid, provisioning still in progress — keep polling"},"404":{"description":"Unknown Checkout Session id"}}}},"/api/v3/billing/webhook":{"post":{"tags":["Billing","Webhooks"],"summary":"Stripe billing webhook (alias)","description":"Public alias for the Stripe webhook endpoint. Stripe sends events here (checkout.session.completed, invoice.paid, subscription.updated, etc.) using Stripe-Signature header for HMAC verification — no JWT required.","security":[],"parameters":[{"in":"header","name":"stripe-signature","required":true,"schema":{"type":"string"},"description":"Stripe webhook signature for HMAC verification"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Stripe event object"}}}},"responses":{"200":{"description":"Event received and processed (or duplicate skipped)"},"400":{"description":"Invalid signature or malformed payload"}}}},"/api/v3/billing/subscription":{"get":{"tags":["Billing"],"summary":"Get current subscription details","description":"Returns the full Stripe subscription object including plan, status, current period dates, and upcoming invoice.","responses":{"200":{"description":"Current subscription details"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/checkout":{"post":{"tags":["Billing"],"summary":"Create a Stripe checkout session","description":"Creates a Stripe Checkout session for initial subscription purchase, returning a redirect URL to the hosted payment page.","responses":{"200":{"description":"Checkout session URL"},"400":{"description":"Invalid plan or request body"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/portal":{"post":{"tags":["Billing"],"summary":"Create a Stripe billing portal session","description":"Creates a Stripe Customer Portal session URL where the tenant can manage their subscription, invoices, and payment methods.","responses":{"200":{"description":"Billing portal session URL"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/trial-status":{"get":{"tags":["Billing"],"summary":"Get current trial status (read-only)","description":"section 7: this endpoint was 404 alongside the\n/trial/start, /trial/convert mutation endpoints. Returns\n`{ isOnTrial, trialEndsAt, daysRemaining, isExpired }`.","responses":{"200":{"description":"Trial status payload"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/portal-link":{"get":{"tags":["Billing"],"summary":"Get a Stripe billing portal session URL (alias of POST /portal)","description":": the in-app \"Manage Plan\" / \"Update Payment Method\" /\n\"View Invoices\" buttons hit `GET /api/v3/billing/portal-link`. This\nalias delegates to the same handler and accepts the optional\n`returnUrl` as a query parameter instead of a body.","parameters":[{"in":"query","name":"returnUrl","required":false,"schema":{"type":"string","format":"uri"},"description":"URL to redirect to after the customer closes the portal."}],"responses":{"200":{"description":"Billing portal session URL"},"400":{"description":"No billing account / Stripe not configured / invalid returnUrl"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/upgrade":{"post":{"tags":["Billing"],"summary":"Upgrade or change subscription plan","description":"Upgrades or changes the tenant's subscription plan. Creates a Stripe Checkout session for new subscriptions or immediately updates an existing subscription with proration.","responses":{"200":{"description":"Plan changed or checkout URL returned"},"400":{"description":"Invalid plan ID or request body"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/change-plan":{"post":{"tags":["Billing"],"summary":"Change subscription plan (alias of /upgrade)","description":"The /upgrade handler already\nsupports both directions (its OpenAPI summary is \"Upgrade or change\nsubscription plan\"); aliasing /change-plan onto the same handler\nlets frontend code and operator scripts use either name.","responses":{"200":{"description":"Plan changed or checkout URL returned"},"400":{"description":"Invalid plan ID or request body"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/upgrade/preview":{"post":{"tags":["Billing"],"summary":"Preview proration cost for a plan change","description":"Calculates the proration amount the tenant would be charged or credited when switching to a different plan, without making any changes.","responses":{"200":{"description":"Proration preview with amount due"},"400":{"description":"Invalid plan ID"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/downgrade":{"post":{"tags":["Billing"],"summary":"Schedule a subscription downgrade at end of billing period","description":"Schedules the tenant's subscription to downgrade to a lower plan at the end of the current billing period without immediate charge.","responses":{"200":{"description":"Downgrade scheduled"},"400":{"description":"Invalid plan or cannot downgrade"},"401":{"description":"Unauthorized"}}}},"/api/v3/billing/usage":{"get":{"tags":["Billing"],"summary":"Get current resource usage counts","description":"Returns the current count of users, properties, and jobs-this-month\nfor the authenticated tenant. Per-seat pricing model — no tier caps,\nso the response intentionally omits any `limit` field. Billing is\nhandled by Stripe quantity sync via EmployeeBillingService.","responses":{"200":{"description":"Array of `{name, current, unit}` records (no limits)"},"401":{"description":"Unauthorized"}}}},"/api/v3/booking/submit":{"post":{"tags":["Booking"],"summary":"Submit a new booking (creates account, contact, and job)","description":"Public endpoint to submit a service booking. Creates a customer account, contact record, and job in one transaction. Rate limited to prevent spam.","responses":{"201":{"description":"Booking created with confirmation number"},"400":{"description":"Invalid booking data"},"429":{"description":"Too many requests"}}}},"/api/v3/booking/{confirmationNumber}":{"get":{"tags":["Booking"],"summary":"Look up a booking by confirmation number","description":"Public endpoint to retrieve booking status and details using the confirmation number sent to the customer. Requires tenant slug for tenant isolation.","parameters":[{"in":"path","name":"confirmationNumber","required":true,"schema":{"type":"string"},"description":"Booking confirmation number"}],"responses":{"200":{"description":"Booking details"},"404":{"description":"Booking not found"}}}},"/api/booking/services":{"get":{"tags":["Booking"],"summary":"Get available services for public booking","description":"Public endpoint returning active price book services enabled for online booking for a given tenant. No authentication required.","parameters":[{"in":"query","name":"tenant","required":true,"schema":{"type":"string"},"description":"Tenant slug identifier"}],"responses":{"200":{"description":"List of available services with pricing and duration"},"400":{"description":"Tenant identifier is required"},"404":{"description":"Tenant not found"}}}},"/api/booking/availability":{"get":{"tags":["Booking"],"summary":"Get available appointment slots for a date","description":"Public endpoint returning available time slots for a given date based on actual technician schedules. Closed on Sundays.","parameters":[{"in":"query","name":"tenant","required":true,"schema":{"type":"string"},"description":"Tenant slug identifier"},{"in":"query","name":"date","required":true,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"Date in YYYY-MM-DD format"},{"in":"query","name":"duration","required":false,"schema":{"type":"integer"},"description":"Service duration in minutes (default 60)"}],"responses":{"200":{"description":"List of available time slots"},"400":{"description":"Invalid date format or missing tenant"},"404":{"description":"Tenant not found"}}}},"/api/v3/branding/assets":{"get":{"tags":["Branding"],"summary":"List branding assets (logos, favicons)","description":"Returns all uploaded branding assets for the current tenant including logos, favicons, and other images.","responses":{"200":{"description":"List of branding assets"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Branding"],"summary":"Upload a branding asset","description":"Upload a new branding asset (logo, favicon, etc.) for the current tenant. Requires admin or owner role.","responses":{"201":{"description":"Asset uploaded successfully"},"400":{"description":"Invalid file or request body"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/assets/{assetId}":{"delete":{"tags":["Branding"],"summary":"Delete a branding asset","description":"Permanently delete a branding asset from storage. Requires admin or owner role.","parameters":[{"in":"path","name":"assetId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Asset ID to delete"}],"responses":{"200":{"description":"Asset deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Asset not found"}}}},"/api/v3/branding":{"get":{"tags":["Branding"],"summary":"Get branding settings","description":"Returns the full branding configuration for the current tenant including colors, fonts, logos, and contact information.","responses":{"200":{"description":"Branding settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Branding"],"summary":"Update branding settings","description":"Update the tenant's branding configuration (colors, fonts, company name, contact info, portal text). Changes are applied globally across the platform.","responses":{"200":{"description":"Branding settings updated"},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/css":{"get":{"tags":["Branding"],"summary":"Get branding CSS variables","description":"Returns CSS custom properties (variables) generated from the tenant's branding configuration for use in frontend themes.","responses":{"200":{"description":"CSS variables as a style block"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/preview":{"post":{"tags":["Branding"],"summary":"Preview branding changes without saving","description":"Generates a preview of branding changes (CSS, rendered components) without persisting the changes to the database.","responses":{"200":{"description":"Preview data for the proposed branding changes"},"400":{"description":"Invalid branding settings"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/test-email":{"post":{"tags":["Branding"],"summary":"Send a test branding email","description":"Sends a test email using the current branding configuration to verify layout, colors, and logos. Requires admin or owner role.","responses":{"200":{"description":"Test email sent"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/domain/verify":{"post":{"tags":["Branding"],"summary":"Verify a custom domain","description":"Initiates DNS verification for a custom domain by adding TXT records and checking DNS propagation.","responses":{"200":{"description":"Verification initiated or domain verified"},"400":{"description":"Invalid domain format"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/domain/check":{"post":{"tags":["Branding"],"summary":"Check DNS status for a custom domain","description":"Checks the current DNS propagation status for a custom domain and returns whether the required records are configured correctly.","responses":{"200":{"description":"DNS status check result"},"400":{"description":"Invalid domain format"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/domain":{"delete":{"tags":["Branding"],"summary":"Remove custom domain configuration","description":"Removes the custom domain configuration and DNS records for the current tenant, reverting to the default platform domain.","responses":{"200":{"description":"Custom domain removed"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/email-templates":{"get":{"tags":["Branding"],"summary":"List all email templates","description":"Returns all available email template types with their current customization status for the current tenant.","responses":{"200":{"description":"List of email templates"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Branding"],"summary":"Create or replace an email template for the current tenant","description":"This endpoint accepts\nthe frontend EmailTemplate row shape, resolves `type` against\nthe backend `EmailTemplateType` enum (frontend aliases like\n`invoice_sent`/`payment_received` reverse-map to `invoice`/\n`payment_receipt`), and upserts via\n`brandingService.saveEmailTemplate`.","responses":{"201":{"description":"Template created (or replaced if a row for this tenant + type already existed)"},"400":{"description":"Validation failed (unsupported type, missing subject/body)"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden — admin or owner role required"}}}},"/api/v3/branding/email-templates/{templateType}":{"get":{"tags":["Branding"],"summary":"Get a specific email template by type","description":"Returns the full HTML/text content and settings for a specific email template type.","parameters":[{"in":"path","name":"templateType","required":true,"schema":{"type":"string"},"description":"Email template type (e.g., invoice, booking_confirmation)"}],"responses":{"200":{"description":"Email template content and settings"},"401":{"description":"Unauthorized"},"404":{"description":"Template type not found"}}},"put":{"tags":["Branding"],"summary":"Update a specific email template","description":"Update the HTML content, subject, and settings for a specific email template. Requires admin or owner role.","parameters":[{"in":"path","name":"templateType","required":true,"schema":{"type":"string"},"description":"Email template type"}],"responses":{"200":{"description":"Email template updated"},"400":{"description":"Invalid template content"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/email-templates/{templateType}/reset":{"post":{"tags":["Branding"],"summary":"Reset an email template to default","description":"Resets a customized email template back to the platform default. Requires admin or owner role.","parameters":[{"in":"path","name":"templateType","required":true,"schema":{"type":"string"},"description":"Email template type to reset"}],"responses":{"200":{"description":"Template reset to default"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/email-templates/{templateType}/preview":{"post":{"tags":["Branding"],"summary":"Preview an email template with sample data","description":"Renders an email template with provided or sample data and returns the HTML output for preview purposes without sending an actual email.","parameters":[{"in":"path","name":"templateType","required":true,"schema":{"type":"string"},"description":"Email template type to preview"}],"responses":{"200":{"description":"Rendered HTML preview"},"400":{"description":"Invalid preview data"},"401":{"description":"Unauthorized"}}}},"/api/v3/branding/email-templates/{templateType}/send-test":{"post":{"tags":["Branding"],"summary":"Send a test render of an email template to the current user","description":"The\nroute now renders the template with the same sample-data\nsubstitution the preview path uses, prefixes the subject\n`[TEST]`, and dispatches via the SendGrid provider. Recipient\nis the JWT user's email address — body-supplied recipients are\nNOT accepted (would otherwise turn the endpoint into a spam\nrelay through the platform sender).","parameters":[{"in":"path","name":"templateType","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Test email dispatched"},"400":{"description":"Email provider not configured or rejected the send"},"401":{"description":"Unauthorized (or no email on JWT user)"},"404":{"description":"Template type not found"}}}},"/api/v3/cache/clear":{"delete":{"tags":["Cache"],"summary":"Clear all cache for the current tenant (admin/owner)","security":[{"bearerAuth":[]}],"description":"Deletes all cached keys for the current tenant from Redis, forcing fresh data to be fetched on the next request. Restricted to admin/owner roles to prevent accidental cache flushing by lower-privilege users.","responses":{"200":{"description":"Number of cache keys deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden — admin or owner role required"}}}},"/api/v3/cache/clear/metrics":{"delete":{"tags":["Cache"],"summary":"Clear only dashboard metrics cache for the current tenant (admin/owner)","security":[{"bearerAuth":[]}],"description":"Selectively clears only the dashboard metrics cache without affecting other cached data, useful for forcing a metrics refresh. Restricted to admin/owner roles.","responses":{"200":{"description":"Metrics cache cleared"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden — admin or owner role required"}}}},"/api/v3/calendar/google/auth":{"get":{"tags":["Calendar"],"summary":"Get Google OAuth authorization URL","description":"Returns a Google OAuth 2.0 authorization URL to initiate the Google Calendar connection flow for the current user.","responses":{"200":{"description":"OAuth authorization URL"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/google/callback":{"get":{"tags":["Calendar"],"summary":"Handle Google OAuth callback (PUBLIC — see publicGoogleCallbackRouter)","description":"Receives the authorization code from Google after the user grants\ncalendar access and exchanges it for tokens. Public-mounted at\n`/v3/calendar/google/callback` before global `authenticate` so\nGoogle's redirect (which carries no JWT) reaches the handler.\nThe HMAC-signed `state` parameter validates the requesting\ntenant/user identity.","parameters":[{"in":"query","name":"code","schema":{"type":"string"},"description":"OAuth authorization code from Google"},{"in":"query","name":"state","schema":{"type":"string"},"description":"HMAC-signed state (CSRF protection + tenant/user identity)"},{"in":"query","name":"error","schema":{"type":"string"},"description":"Error code if user denied access"}],"responses":{"302":{"description":"Redirect to settings page after successful connection"},"400":{"description":"Invalid or missing authorization code"}}}},"/api/v3/calendar/google/events":{"get":{"tags":["Calendar"],"summary":"List Google Calendar events","description":"Retrieves events from the connected Google Calendar within the specified date range.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Filter events from this date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Filter events until this date"}],"responses":{"200":{"description":"List of calendar events"},"401":{"description":"Unauthorized"},"404":{"description":"Google Calendar not connected"}}},"post":{"tags":["Calendar"],"summary":"Create a Google Calendar event","description":"Creates a new event on the connected Google Calendar. Requires calendar:create permission.","responses":{"201":{"description":"Event created"},"400":{"description":"Invalid event data"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/google/events/{eventId}":{"patch":{"tags":["Calendar"],"summary":"Update a Google Calendar event","description":"Updates an existing Google Calendar event. Requires calendar:update permission.","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"type":"string"},"description":"Google Calendar event ID"}],"responses":{"200":{"description":"Event updated"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}},"delete":{"tags":["Calendar"],"summary":"Delete a Google Calendar event","description":"Permanently deletes an event from Google Calendar. Requires calendar:delete permission.","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"type":"string"},"description":"Google Calendar event ID"}],"responses":{"200":{"description":"Event deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/api/v3/calendar/google/availability":{"get":{"tags":["Calendar"],"summary":"Get availability from Google Calendar","description":"Returns free/busy time blocks from Google Calendar for scheduling purposes, based on the connected calendar's events.","responses":{"200":{"description":"Availability slots"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/google/sync":{"post":{"tags":["Calendar"],"summary":"Trigger a Google Calendar sync","description":"Manually triggers a full sync of jobs and appointments to/from Google Calendar for the current user. Requires calendar:update permission.","responses":{"200":{"description":"Sync triggered"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/google/disconnect":{"post":{"tags":["Calendar"],"summary":"Disconnect Google Calendar integration","description":"Revokes Google Calendar OAuth tokens and removes the integration, stopping all syncing. Requires calendar:delete permission.","responses":{"200":{"description":"Google Calendar disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/google/watch":{"post":{"tags":["Calendar"],"summary":"Set up Google Calendar webhook watch","description":"Registers a Google Calendar push notification channel to receive real-time event change notifications. Requires calendar:update permission.","responses":{"200":{"description":"Watch channel registered"},"401":{"description":"Unauthorized"}}},"delete":{"tags":["Calendar"],"summary":"Stop Google Calendar webhook watch","description":"Stops the active Google Calendar push notification channel, disabling real-time change notifications. Requires calendar:update permission.","responses":{"200":{"description":"Watch channel stopped"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/outlook/auth":{"get":{"tags":["Calendar"],"summary":"Get Outlook OAuth authorization URL","description":"Returns a Microsoft OAuth 2.0 authorization URL to initiate the Outlook Calendar connection flow for the current user.","responses":{"200":{"description":"OAuth authorization URL"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/outlook/callback":{"get":{"tags":["Calendar"],"summary":"Handle Outlook OAuth callback (PUBLIC — see publicOutlookCallbackRouter)","description":"Receives the authorization code from Microsoft after the user\ngrants Outlook calendar access and exchanges it for tokens.\nPublic-mounted at `/v3/calendar/outlook/callback` before global\n`authenticate` so Microsoft's redirect (which carries no JWT)\nreaches the handler. Returns an HTML page that signals the\nopener window via `postMessage` and self-closes.","responses":{"200":{"description":"HTML self-closing popup that posts message to opener"},"400":{"description":"Invalid or missing authorization code"}}}},"/api/v3/calendar/outlook/events":{"get":{"tags":["Calendar"],"summary":"List Outlook Calendar events","description":"Retrieves events from the connected Outlook Calendar within the specified date range.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Filter events from this date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Filter events until this date"}],"responses":{"200":{"description":"List of calendar events"},"401":{"description":"Unauthorized"},"404":{"description":"Outlook Calendar not connected"}}},"post":{"tags":["Calendar"],"summary":"Create an Outlook Calendar event","description":"Creates a new event on the connected Outlook Calendar. Requires calendar:create permission.","responses":{"201":{"description":"Event created"},"400":{"description":"Invalid event data"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/outlook/events/{eventId}":{"patch":{"tags":["Calendar"],"summary":"Update an Outlook Calendar event","description":"Updates an existing Outlook Calendar event. Requires calendar:update permission.","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"type":"string"},"description":"Outlook event ID"}],"responses":{"200":{"description":"Event updated"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}},"delete":{"tags":["Calendar"],"summary":"Delete an Outlook Calendar event","description":"Permanently deletes an event from Outlook Calendar. Requires calendar:delete permission.","parameters":[{"in":"path","name":"eventId","required":true,"schema":{"type":"string"},"description":"Outlook event ID"}],"responses":{"200":{"description":"Event deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/api/v3/calendar/outlook/availability":{"get":{"tags":["Calendar"],"summary":"Get availability from Outlook Calendar","description":"Returns free/busy time blocks from Outlook Calendar for scheduling purposes, based on the connected calendar's events.","responses":{"200":{"description":"Availability slots"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/outlook/sync":{"post":{"tags":["Calendar"],"summary":"Trigger an Outlook Calendar sync","description":"Manually triggers a full sync of jobs and appointments to/from Outlook Calendar for the current user. Requires calendar:update permission.","responses":{"200":{"description":"Sync triggered"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/outlook/disconnect":{"post":{"tags":["Calendar"],"summary":"Disconnect Outlook Calendar integration","description":"Revokes Microsoft OAuth tokens and removes the Outlook Calendar integration, stopping all syncing. Requires calendar:delete permission.","responses":{"200":{"description":"Outlook Calendar disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/settings":{"get":{"tags":["Calendar"],"summary":"Get calendar sync settings","description":"Returns the current calendar synchronization settings including timezone, import/export flags, and sync preferences.","responses":{"200":{"description":"Calendar sync settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Calendar"],"summary":"Update calendar sync settings","description":"Updates calendar synchronization settings such as timezone, sync direction, and event import/export preferences. Requires calendar:update permission.","responses":{"200":{"description":"Settings updated"},"400":{"description":"Invalid settings"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/sync-history":{"get":{"tags":["Calendar"],"summary":"Get calendar sync history log","description":"Returns the history of calendar sync operations with timestamps, sync type, and result status for debugging integration issues.","responses":{"200":{"description":"List of sync history entries"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/status":{"get":{"tags":["Calendar"],"summary":"Get calendar integration status","description":"Returns the connection status for all supported calendar providers (Google Calendar, Outlook) for the current user.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection status for each calendar provider"},"401":{"description":"Unauthorized"}}}},"/api/v3/calendar/google/webhook":{"post":{"tags":["Calendar"],"summary":"Receive Google Calendar push notification webhook","description":"Endpoint called by Google when calendar events change. Verifies the channel against the database before processing and queues a sync job.","responses":{"200":{"description":"Notification acknowledged"},"400":{"description":"Invalid notification headers"},"403":{"description":"Channel verification failed"}}}},"/api/v3/communication/logs/stats":{"get":{"tags":["Communication"],"summary":"Get message delivery statistics summary","description":"Returns aggregate delivery statistics (sent, delivered, failed, open rates) for SMS and email messages.","responses":{"200":{"description":"Delivery statistics summary"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/logs":{"get":{"tags":["Communication"],"summary":"List message delivery logs","description":"Returns paginated message delivery logs with status, channel, recipient, and timestamp for auditing and debugging.","parameters":[{"in":"query","name":"page","schema":{"type":"integer"},"description":"Page number"},{"in":"query","name":"channel","schema":{"type":"string","enum":["sms","email"]},"description":"Filter by communication channel"}],"responses":{"200":{"description":"Paginated delivery logs"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/analytics":{"get":{"tags":["Communication"],"summary":"Get communication analytics overview","description":"Returns detailed analytics including open rates, click rates, response rates, and channel performance breakdowns for the specified period.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Analytics period start date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Analytics period end date"}],"responses":{"200":{"description":"Communication analytics data"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/email/send":{"post":{"tags":["Communication"],"summary":"Send an email with optional file attachment","description":"Sends a direct email to a customer with optional PDF attachment (max 10MB). Used for invoices, estimates, and ad-hoc email. Requires communication:send permission.","responses":{"200":{"description":"Email sent successfully"},"400":{"description":"Invalid recipient or attachment"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/send":{"post":{"tags":["Communication"],"summary":"Send a message to a customer","description":"Sends an SMS or email message to a customer using a template or custom content. Supports scheduled delivery.","responses":{"200":{"description":"Message sent or scheduled"},"400":{"description":"Invalid recipient or message content"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/send/bulk":{"post":{"tags":["Communication"],"summary":"Send bulk messages to multiple customers","description":"Sends the same SMS or email message to multiple customers at once. Supports template-based personalization and scheduled delivery.","responses":{"200":{"description":"Bulk messages queued"},"400":{"description":"Invalid recipients or message content"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/messages/{id}":{"delete":{"tags":["Communication"],"summary":"Cancel a scheduled message","description":"Cancels a pending or scheduled message before it is sent. Already-sent messages cannot be cancelled.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Message ID to cancel"}],"responses":{"200":{"description":"Message cancelled"},"401":{"description":"Unauthorized"},"404":{"description":"Message not found or already sent"}}}},"/api/v3/communication/recipients/{type}/{id}/preferences":{"get":{"tags":["Communication"],"summary":"Get communication preferences for a recipient","description":"Returns the opt-in/opt-out preferences and channel settings for a specific recipient (customer or contact).","parameters":[{"in":"path","name":"type","required":true,"schema":{"type":"string","enum":["customer","contact"]},"description":"Recipient type"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Recipient ID"}],"responses":{"200":{"description":"Communication preferences"},"401":{"description":"Unauthorized"},"404":{"description":"Recipient not found"}}},"put":{"tags":["Communication"],"summary":"Update communication preferences for a recipient","description":"Updates opt-in/opt-out settings and preferred communication channels for a specific recipient.","parameters":[{"in":"path","name":"type","required":true,"schema":{"type":"string","enum":["customer","contact"]},"description":"Recipient type"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Recipient ID"}],"responses":{"200":{"description":"Preferences updated"},"400":{"description":"Invalid preferences data"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/timing/analytics":{"get":{"tags":["Communication"],"summary":"Get smart timing analytics for optimal send times","description":"Returns engagement analytics by hour/day for the current tenant to identify optimal message send times.","responses":{"200":{"description":"Timing analytics with engagement data"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/timing/calculate":{"post":{"tags":["Communication"],"summary":"Calculate optimal send time for a message","description":"Calculates the recommended send time for a message based on recipient engagement history and smart timing data.","responses":{"200":{"description":"Recommended send timestamp"},"400":{"description":"Invalid calculation request"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/templates":{"get":{"tags":["Communication"],"summary":"List all communication templates","description":"Returns all SMS and email templates for the current tenant including trigger events, channels, and content.","responses":{"200":{"description":"List of communication templates"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Communication"],"summary":"Create a new communication template","description":"Creates a new SMS or email template with trigger event association. Requires admin or owner role.","responses":{"201":{"description":"Template created"},"400":{"description":"Invalid template data"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/templates/trigger/{event}":{"get":{"tags":["Communication"],"summary":"Get communication templates by trigger event","description":"Returns all active templates associated with a specific trigger event (e.g., job_completed, invoice_sent), optionally filtered by channel.","parameters":[{"in":"path","name":"event","required":true,"schema":{"type":"string"},"description":"Trigger event name"},{"in":"query","name":"channel","schema":{"type":"string","enum":["sms","email"]},"description":"Filter by channel"}],"responses":{"200":{"description":"Templates for the trigger event"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/templates/{id}":{"get":{"tags":["Communication"],"summary":"Get a specific communication template by ID","description":"Returns the full content and settings for a specific communication template.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"}],"responses":{"200":{"description":"Template details"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"}}},"put":{"tags":["Communication"],"summary":"Update a communication template","description":"Replaces a communication template's content and settings. Requires admin or owner role.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"}],"responses":{"200":{"description":"Template updated"},"400":{"description":"Invalid template data"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"}}},"patch":{"tags":["Communication"],"summary":"Partially update a communication template","description":"Partially updates a communication template's content or settings. Requires admin or owner role.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"}],"responses":{"200":{"description":"Template updated"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"}}},"delete":{"tags":["Communication"],"summary":"Delete a communication template","description":"Permanently deletes a communication template. Requires admin or owner role.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"}],"responses":{"200":{"description":"Template deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"}}}},"/api/v3/communication/templates/preview":{"post":{"tags":["Communication"],"summary":"Preview a communication template with sample data","description":"Renders a communication template with provided variable values and returns the fully formatted message without sending it.","responses":{"200":{"description":"Rendered template preview"},"400":{"description":"Invalid template or preview data"},"401":{"description":"Unauthorized"}}}},"/api/v3/communication/templates/{id}/test":{"post":{"tags":["Communication"],"summary":"Send a test dispatch of a communication template to the authenticated user","description":"Renders the template with sample context and dispatches it via the matching channel\nprovider (email → SendGrid to JWT user's email, sms → Twilio to JWT user's stored\nphone). push/in_app templates return BadRequestError. Requires admin or owner role.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"}],"responses":{"200":{"description":"Test message dispatched"},"400":{"description":"Provider not configured, push/in_app channel, or provider rejected dispatch"},"401":{"description":"Unauthorized — no tenant or no authenticated user"},"404":{"description":"Template not found"}}}},"/api/v3/contractor-portal/stats":{"get":{"tags":["Contractor Portal"],"summary":"Get contractor statistics","description":"Returns job statistics for the authenticated contractor including total, pending, in-progress, completed, and declined jobs for the current month. Non-contractor callers (Owner / Admin preview) receive the zero-state envelope instead of 403/500.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Contractor job statistics (real values for contractors, zeros for non-contractors)"},"401":{"description":"Unauthorized"}}}},"/api/conversations":{"post":{"tags":["Conversations"],"summary":"Start a new conversation","description":"Creates a new AI-assisted conversation thread for a customer interaction, optionally associating it with a lead source.","responses":{"201":{"description":"Conversation created"},"400":{"description":"Invalid conversation data"},"401":{"description":"Unauthorized"}}},"get":{"tags":["Conversations"],"summary":"List conversations with filters","description":"Returns paginated conversations for the current tenant with optional filters for status, assigned user, and date range.","parameters":[{"in":"query","name":"status","schema":{"type":"string","enum":["open","closed","pending"]},"description":"Filter by conversation status"},{"in":"query","name":"page","schema":{"type":"integer"},"description":"Page number"}],"responses":{"200":{"description":"Paginated conversation list"},"401":{"description":"Unauthorized"}}}},"/api/conversations/{id}/messages":{"post":{"tags":["Conversations"],"summary":"Add a message to a conversation","description":"Appends a new inbound or outbound message to the conversation thread and triggers AI analysis if in auto mode.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"201":{"description":"Message added"},"400":{"description":"Invalid message data"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}},"get":{"tags":["Conversations"],"summary":"Get messages for a conversation","description":"Returns messages in a conversation thread ordered chronologically with sender metadata. Without query params the whole thread is returned. Pass `limit` to load only the newest page (fast first render); pass `before`+`beforeId` alongside `limit` to page backwards through older messages (keyset cursor). Paginated responses include a `pagination` block with `hasMore` and `nextCursor`.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Newest-page size. Omit for the full thread."},{"in":"query","name":"before","schema":{"type":"string","format":"date-time"},"description":"Keyset cursor — created_at of the oldest message already loaded."},{"in":"query","name":"beforeId","schema":{"type":"string","format":"uuid"},"description":"Keyset cursor tiebreaker — id of the oldest message already loaded."}],"responses":{"200":{"description":"Conversation messages"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}}},"/api/conversations/{id}/analyze":{"post":{"tags":["Conversations"],"summary":"Record an analysis result for a conversation","description":"Stores an AI analysis result (intent, sentiment, lead score) for a conversation without triggering a new analysis.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"200":{"description":"Analysis recorded"},"400":{"description":"Invalid analysis data"},"401":{"description":"Unauthorized"}}}},"/api/conversations/{id}/qualify":{"post":{"tags":["Conversations"],"summary":"Qualify a lead from a conversation","description":"Marks a conversation as a qualified lead, creating a lead record and triggering lead follow-up workflows.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"200":{"description":"Lead qualified"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}}},"/api/conversations/{id}/close":{"post":{"tags":["Conversations"],"summary":"Close a conversation","description":"Closes an active conversation thread with an optional resolution reason, stopping AI processing.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"200":{"description":"Conversation closed"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}}},"/api/conversations/{id}/analyze-ai":{"post":{"tags":["Conversations"],"summary":"Run AI analysis on a conversation","description":"Triggers an AI analysis of the conversation using the V2 analysis pipeline. Feature-flagged endpoint.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"200":{"description":"AI analysis result"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}}},"/api/conversations/{id}":{"patch":{"tags":["Conversations"],"summary":"Update conversation fields","description":"Updates mutable conversation fields such as status, assigned user, or metadata.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"200":{"description":"Conversation updated"},"400":{"description":"Invalid update data"},"401":{"description":"Unauthorized"}}},"delete":{"tags":["Conversations"],"summary":"Soft-delete a conversation","description":"Marks a conversation as deleted (`status = 'deleted'`) without\nremoving rows — preserves audit history while hiding the\nconversation from list/detail views.: the\nConversations (`/chat`) page had no Delete affordance, so\norphan conversations from misrouted webhooks accumulated\nforever. The repository's tenant-scoped `WHERE` enforces\nmulti-tenant isolation.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"204":{"description":"Conversation soft-deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found (or owned by another tenant)"}}},"get":{"tags":["Conversations"],"summary":"Get conversation details by ID","description":"Returns full conversation details including customer info, AI analysis, lead status, and message count.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"responses":{"200":{"description":"Conversation details"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}}},"/api/conversations/{id}/mode-override":{"put":{"tags":["Conversations"],"summary":"Set or clear the per-conversation AI mode override (LB","description":"Pins a single conversation to a specific AUTO / ASSIST / MANUAL\nmode that wins over the tenant-level default. Pass `mode: null`\nto clear the override and fall back to the tenant default for\nsubsequent messages. The override is visible to every employee\nviewing this conversation — it is a per-conversation product\ndecision, not a per-employee preference.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","nullable":true,"enum":["auto","assist","manual",null]}}}}}},"responses":{"200":{"description":"Override set or cleared"},"400":{"description":"Invalid mode value"},"401":{"description":"Unauthorized"},"404":{"description":"Conversation not found"}}}},"/api/conversations/{id}/read":{"post":{"tags":["Conversations"],"summary":"Mark a conversation read","description":"Resets the unread-count badge (sets the shared `last_read_at` marker to\nnow) and returns the previous marker so the chat can scroll to the first\nunseen message and render the \"Unread messages\" divider. View-level\naction — any user who can open the conversation may mark it read.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Marked read; returns previousLastReadAt (nullable ISO timestamp)"},"404":{"description":"Conversation not found"}}}},"/api/conversations/leads":{"get":{"tags":["Conversations"],"summary":"Get conversations identified as leads","description":"Returns conversations that have been qualified as leads, with lead score and recommended follow-up actions.","responses":{"200":{"description":"List of lead conversations"},"401":{"description":"Unauthorized"}}}},"/api/conversations/metrics":{"get":{"tags":["Conversations"],"summary":"Get conversation metrics and statistics","description":"Returns aggregate metrics including total conversations, resolution rate, average response time, and lead conversion rate.","responses":{"200":{"description":"Conversation metrics"},"401":{"description":"Unauthorized"}}}},"/api/conversations/sender-numbers":{"get":{"tags":["Conversations"],"summary":"List the tenant's SMS-capable \"send from\" numbers","description":"Active, SMS-capable tenant numbers (primary first) for the conversation composer's outbound sender picker. Auto is a client-side sentinel and is not returned.","responses":{"200":{"description":"Sender numbers"},"401":{"description":"Unauthorized"}}}},"/api/conversations/{id}/messages/search":{"get":{"tags":["Conversations"],"summary":"Search messages within a conversation","description":"Full-text search through messages in a specific conversation thread.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Conversation ID"},{"in":"query","name":"q","required":true,"schema":{"type":"string"},"description":"Search query"}],"responses":{"200":{"description":"Matching messages"},"401":{"description":"Unauthorized"}}}},"/api/customers/check-duplicates":{"get":{"tags":["Customers"],"summary":"Check for potential duplicate customers","description":"Scans the customer database for potential duplicates based on name, email, and phone number similarity.","responses":{"200":{"description":"List of potential duplicate customer pairs"},"401":{"description":"Unauthorized"}}}},"/api/customers/merge":{"post":{"tags":["Customers"],"summary":"Merge two duplicate customers into one","description":"Merges two customer records into one, transferring all jobs, invoices, and notes. The secondary customer is deleted. Requires customers:delete permission.","responses":{"200":{"description":"Customers merged successfully"},"400":{"description":"Invalid customer IDs or cannot merge"},"401":{"description":"Unauthorized"},"404":{"description":"One or both customers not found"}}}},"/api/customers/{id}/notes":{"put":{"tags":["Customers"],"summary":"Update customer notes (legacy endpoint)","description":"Legacy endpoint to replace the entire notes field for a customer. Use POST for individual note management.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer ID"}],"responses":{"200":{"description":"Notes updated"},"401":{"description":"Unauthorized"},"404":{"description":"Customer not found"}}},"get":{"tags":["Customers"],"summary":"Get notes for a customer","description":"Returns all notes for a customer, ordered with pinned notes first.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer ID"}],"responses":{"200":{"description":"List of customer notes"},"401":{"description":"Unauthorized"},"404":{"description":"Customer not found"}}},"post":{"tags":["Customers"],"summary":"Add a note to a customer with optional @mentions","description":"Creates a new note for a customer. Supports @mentions of team members which trigger notifications.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer ID"}],"responses":{"201":{"description":"Note created"},"400":{"description":"Invalid note content"},"401":{"description":"Unauthorized"}}}},"/api/customers/{id}/notes/{noteId}/pin":{"patch":{"tags":["Customers"],"summary":"Toggle pin on a customer note","description":"Toggles the pinned status of a note, pinned notes appear at the top of the notes list.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer ID"},{"in":"path","name":"noteId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Note ID"}],"responses":{"200":{"description":"Pin status toggled"},"401":{"description":"Unauthorized"},"404":{"description":"Note not found"}}}},"/api/customers/{id}/notes/{noteId}":{"delete":{"tags":["Customers"],"summary":"Delete a customer note","description":"Permanently deletes a specific note from a customer record. Requires customers:delete permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer ID"},{"in":"path","name":"noteId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Note ID"}],"responses":{"200":{"description":"Note deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Note not found"}}}},"/api/v3/customers/{id}/portal-invite":{"post":{"tags":["Customers"],"summary":"Send portal invitation to customer","description":"Sends a magic link email invitation to a customer to access the self-service tenant portal for viewing invoices and job status.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Customer ID"}],"responses":{"200":{"description":"Invitation sent"},"400":{"description":"Invalid email address"},"401":{"description":"Unauthorized"},"404":{"description":"Customer not found"}}}},"/api/v3/dashboard/metrics":{"get":{"tags":["Dashboard"],"summary":"Get dashboard metrics","description":"Returns high-level KPI metrics for the current tenant including revenue, active jobs, and customer counts. Cached for performance.","responses":{"200":{"description":"Dashboard KPI metrics"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/stats":{"get":{"tags":["Dashboard"],"summary":"Get dashboard stats","description":"Returns aggregate counts and totals for the current tenant including jobs, invoices, customers, and revenue for the current period.","responses":{"200":{"description":"Dashboard aggregate stats"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/activity":{"get":{"tags":["Dashboard"],"summary":"Get recent activity feed","description":"Returns a chronological feed of recent tenant activity including job updates, invoice payments, new customers, and system events.","responses":{"200":{"description":"Recent activity feed"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/conversation-stats":{"get":{"tags":["Dashboard"],"summary":"Get conversation statistics","description":"Returns conversation-specific stats including open threads, leads generated, AI resolution rate, and average response time.","responses":{"200":{"description":"Conversation statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/job-stats":{"get":{"tags":["Dashboard"],"summary":"Get job statistics","description":"Returns job completion metrics including status breakdown, average completion time, and technician utilization for the current period.","responses":{"200":{"description":"Job statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/revenue-trend":{"get":{"tags":["Dashboard"],"summary":"Get revenue trend data","description":"Returns time-series revenue data for charting trends over a configurable period (daily, weekly, monthly).","parameters":[{"in":"query","name":"period","schema":{"type":"string","enum":["7d","30d","90d","1y"]},"description":"Trend period"}],"responses":{"200":{"description":"Revenue trend time-series data"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/top-services":{"get":{"tags":["Dashboard"],"summary":"Get top services by revenue","description":"Returns the highest-revenue services ranked by total income and job count for the current period.","parameters":[{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Number of top services to return (default 10)"}],"responses":{"200":{"description":"Top services ranked by revenue"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/upcoming-jobs":{"get":{"tags":["Dashboard"],"summary":"Get upcoming jobs","description":"Returns scheduled jobs for the next 7 days ordered by start time with technician and customer details.","responses":{"200":{"description":"List of upcoming jobs"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/in-progress-jobs":{"get":{"tags":["Dashboard"],"summary":"Get in-progress jobs","description":"Returns currently active jobs with technician location and estimated completion time.","responses":{"200":{"description":"List of in-progress jobs"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/overdue-invoices":{"get":{"tags":["Dashboard"],"summary":"Get overdue invoices","description":"Returns unpaid invoices past their due date, sorted by days overdue descending.","responses":{"200":{"description":"List of overdue invoices"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/invoice-aging":{"get":{"tags":["Dashboard"],"summary":"Get invoice aging report","description":"Returns outstanding invoice amounts grouped into aging buckets (0-30, 31-60, 61-90, 90+ days) for accounts receivable management.","responses":{"200":{"description":"Invoice aging buckets with totals"},"401":{"description":"Unauthorized"}}}},"/api/v3/dashboard/estimate-conversion":{"get":{"tags":["Dashboard"],"summary":"Get estimate conversion rate","description":"Returns the ratio of estimates converted to paid jobs, broken down by service category and time period.","responses":{"200":{"description":"Estimate conversion rate and breakdown"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/auto/{jobId}":{"post":{"tags":["Dispatch"],"summary":"Trigger auto-dispatch for a job","description":"AI selects and assigns the best available technician for a job based on skills, location, availability, and workload. Requires dispatch:create permission.","parameters":[{"in":"path","name":"jobId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID to auto-dispatch"}],"responses":{"200":{"description":"Technician assigned"},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Job not found or no available technicians"}}}},"/api/v3/dispatch/assign":{"post":{"tags":["Dispatch"],"summary":"Manually assign a technician to a job","description":"Creates a manual assignment linking a specific technician to a job and sends them a notification. Requires dispatch:create permission.","responses":{"200":{"description":"Assignment created"},"400":{"description":"Invalid assignment data"},"401":{"description":"Unauthorized"},"404":{"description":"Job or technician not found"}}}},"/api/v3/dispatch/response/{assignmentId}":{"post":{"tags":["Dispatch"],"summary":"Record technician's response to assignment","description":"Records whether a technician accepted or declined a dispatch assignment. Updates job status accordingly. Requires dispatch:update permission.","parameters":[{"in":"path","name":"assignmentId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Assignment ID"}],"responses":{"200":{"description":"Response recorded"},"400":{"description":"Invalid response data"},"401":{"description":"Unauthorized"},"404":{"description":"Assignment not found"}}}},"/api/v3/dispatch/config":{"get":{"tags":["Dispatch"],"summary":"Get dispatch configuration","description":"Returns the tenant's dispatch configuration including auto-dispatch rules, skill matching weights, and notification settings.","responses":{"200":{"description":"Dispatch configuration"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Dispatch"],"summary":"Update dispatch configuration","description":"Updates the tenant-wide dispatch configuration. Requires admin or owner role.","responses":{"200":{"description":"Configuration updated"},"400":{"description":"Invalid configuration data"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/technicians/{id}/location":{"put":{"tags":["Dispatch"],"summary":"Update technician location","description":"Updates a technician's current GPS coordinates for real-time tracking on the dispatch map.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"}],"responses":{"200":{"description":"Location updated"},"400":{"description":"Invalid coordinates"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/locations":{"get":{"tags":["Dispatch"],"summary":"Get technician locations","description":"Returns current GPS coordinates and status for all active technicians for the dispatch map view.","responses":{"200":{"description":"List of technician locations with coordinates"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/analytics":{"get":{"tags":["Dispatch"],"summary":"Get dispatch analytics","description":"Returns dispatch performance metrics including assignment acceptance rates, average response times, and technician utilization.","responses":{"200":{"description":"Dispatch analytics data"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/technicians/{id}/skills":{"get":{"tags":["Dispatch"],"summary":"Get technician skills","description":"Returns the list of skills and certifications for a specific technician used in auto-dispatch matching.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"}],"responses":{"200":{"description":"Technician skill list"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Dispatch"],"summary":"Add skill to technician","description":"Adds a new skill or certification to a technician's profile for dispatch matching.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"}],"responses":{"201":{"description":"Skill added"},"400":{"description":"Invalid skill data"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/technicians/{id}/skills/{skillId}":{"put":{"tags":["Dispatch"],"summary":"Update technician skill","description":"Updates the proficiency level or certification details for a technician's existing skill.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"},{"in":"path","name":"skillId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Skill ID"}],"responses":{"200":{"description":"Skill updated"},"400":{"description":"Invalid skill data"},"401":{"description":"Unauthorized"},"404":{"description":"Skill not found"}}},"delete":{"tags":["Dispatch"],"summary":"Remove technician skill","description":"Removes a skill from a technician's profile. Uses the dispatch `update` action (no separate delete action in the seed).","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"skillId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Skill removed"},"401":{"description":"Unauthorized"},"404":{"description":"Skill not found"}}}},"/api/v3/dispatch/technicians/{id}/certifications":{"get":{"tags":["Dispatch"],"summary":"Get technician certifications","description":"Returns the certifications on file for a technician.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Certification list"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/technicians/{id}/performance":{"get":{"tags":["Dispatch"],"summary":"Get technician performance metrics","description":"Returns the rolling performance metrics for a technician (jobs, ratings, first-time-fix, on-time arrival).","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Performance metrics (or null when none recorded)"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/skill-categories":{"get":{"tags":["Dispatch"],"summary":"List skill categories","description":"Returns all available skill categories for organizing technician skills and certifications.","responses":{"200":{"description":"List of skill categories"},"401":{"description":"Unauthorized"}}}},"/api/v3/dispatch/suggestions/{jobId}":{"get":{"tags":["Dispatch"],"summary":"Get AI-powered technician suggestions for a job","description":"Returns ranked list of technicians with scoring breakdown","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"jobId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"limit","schema":{"type":"integer","default":5,"maximum":20}},{"in":"query","name":"includeOffDuty","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Ranked technician suggestions"}}}},"/api/v3/documents":{"get":{"tags":["Documents"],"summary":"List tenant documents (stub — always empty until storage lands)","description":"Returns an empty array. Real implementation pending — see issue","responses":{"200":{"description":"Empty document list (placeholder)"},"401":{"description":"Unauthorized"}}}},"/api/v3/email-tracking/stats":{"get":{"tags":["Email Tracking"],"summary":"Get email tracking stats","description":"Returns aggregate email tracking statistics including open rates, click rates, and delivery rates for the specified period.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Period start date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Period end date"}],"responses":{"200":{"description":"Email tracking statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/email-tracking/stats/{emailId}":{"get":{"tags":["Email Tracking"],"summary":"Get stats for a specific email","description":"Returns detailed tracking events (opens, clicks, bounces) for a specific sent email identified by tracking ID.","parameters":[{"in":"path","name":"emailId","required":true,"schema":{"type":"string"},"description":"Email tracking ID"}],"responses":{"200":{"description":"Individual email tracking events"},"401":{"description":"Unauthorized"},"404":{"description":"Email tracking record not found"}}}},"/api/v3/email-tracking/pixel/{token}.gif":{"get":{"tags":["Email Tracking"],"summary":"Track email open via pixel","description":"Records an email open event when the 1x1 tracking pixel is loaded by the recipient's email client. Returns a transparent GIF.","parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"},"description":"Email tracking token"}],"responses":{"200":{"description":"1x1 transparent GIF image"}}}},"/api/v3/email-tracking/click/{token}":{"get":{"tags":["Email Tracking"],"summary":"Track email link click and redirect","description":"Records a link click event from an email and redirects the user to the original destination URL.","parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"},"description":"Email click tracking token"}],"responses":{"302":{"description":"Redirect to the original link destination"},"404":{"description":"Invalid or expired tracking token"}}}},"/api/v3/email-tracking/webhooks/sendgrid":{"post":{"tags":["Email Tracking"],"summary":"Handle SendGrid webhook events","description":"Receives email event notifications (delivered, bounced, opened, clicked, unsubscribed) from SendGrid. Verifies HMAC-SHA256 signature before processing.","responses":{"200":{"description":"Events processed"},"401":{"description":"Invalid signature"},"503":{"description":"Webhook verification key not configured"}}}},"/api/v3/email-tracking/webhooks/resend":{"post":{"tags":["Email Tracking"],"summary":"Handle Resend webhook events","description":"Receives email lifecycle events from Resend (sent/delivered/opened/clicked/bounced/complained). Uses the svix signature scheme (whsec_... secret).","responses":{"200":{"description":"Event processed"},"401":{"description":"Invalid signature"},"503":{"description":"Webhook secret not configured"}}}},"/api/v3/feedback":{"get":{"tags":["Feedback"],"summary":"List AI agent feedback","description":"Returns paginated feedback entries submitted by users for AI agent responses. Requires admin, manager, or owner role.","parameters":[{"in":"query","name":"page","schema":{"type":"integer"},"description":"Page number"}],"responses":{"200":{"description":"Paginated feedback list"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Feedback"],"summary":"Record AI agent feedback","description":"Submits a thumbs-up/thumbs-down rating and optional comment for an AI agent response to support quality monitoring.","responses":{"201":{"description":"Feedback recorded"},"400":{"description":"Invalid feedback data"},"401":{"description":"Unauthorized"}}}},"/api/v3/feedback/stats":{"get":{"tags":["Feedback"],"summary":"Get feedback statistics","description":"Returns aggregate AI feedback statistics including average rating, satisfaction rate, and feedback volume by agent type.","responses":{"200":{"description":"Feedback aggregate statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/feedback/recent":{"get":{"tags":["Feedback"],"summary":"Get recent feedback entries","description":"Returns the most recent AI feedback entries, useful for quick review of agent quality.","parameters":[{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Number of recent entries to return"}],"responses":{"200":{"description":"Recent feedback entries"},"401":{"description":"Unauthorized"}}}},"/api/v3/feedback/low-rated":{"get":{"tags":["Feedback"],"summary":"Get low-rated AI responses","description":"Returns AI responses with negative feedback for quality improvement analysis. Requires admin, manager, or owner role.","responses":{"200":{"description":"Low-rated feedback entries"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups":{"post":{"tags":["Follow-Ups"],"summary":"Create a new follow-up task","description":"Creates a new follow-up task associated with a customer context (job, conversation, estimate). Requires admin, manager, or owner role.","responses":{"201":{"description":"Follow-up task created"},"400":{"description":"Invalid follow-up data"},"401":{"description":"Unauthorized"}}},"get":{"tags":["Follow-Ups"],"summary":"List follow-ups (paginated)","description":"BUG-FU-010: the root path is the canonical paginated list.\nUse `/stats` for the aggregate summary. `/due` is retained for\nback-compat but is bounded at 12 items without pagination — prefer\n`GET /?limit=…&offset=…&status=…` for any list consumer that needs\nmore than the first page.","responses":{"200":{"description":"Paginated list of follow-up tasks"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/context/{contextId}/cancel-all":{"post":{"tags":["Follow-Ups"],"summary":"Cancel all pending follow-ups for a context","description":"Cancels all pending follow-up tasks associated with a specific context (e.g., after a job is completed). Requires admin, manager, or owner role.","parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"},"description":"Context ID (job ID, conversation ID, etc.)"}],"responses":{"200":{"description":"Number of cancelled follow-ups"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/{id}":{"get":{"tags":["Follow-Ups"],"summary":"Get follow-up task by ID","description":"Returns the full details of a specific follow-up task including status, scheduled time, and associated context.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Follow-up task details"},"401":{"description":"Unauthorized"},"404":{"description":"Follow-up not found"}}}},"/api/v3/follow-ups/context/{contextId}":{"get":{"tags":["Follow-Ups"],"summary":"Get all follow-ups for a context","description":"Returns all follow-up tasks (any status) associated with a specific context such as a job or conversation.","parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"},"description":"Context ID"}],"responses":{"200":{"description":"List of follow-up tasks for the context"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/context/{contextId}/pending":{"get":{"tags":["Follow-Ups"],"summary":"Get pending follow-ups for a context","description":"Returns only pending (not yet executed) follow-up tasks for a specific context.","parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"},"description":"Context ID"}],"responses":{"200":{"description":"List of pending follow-up tasks"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/context/{contextId}/summary":{"get":{"tags":["Follow-Ups"],"summary":"Get follow-up summary for a context","description":"Returns a summary count of follow-ups by status (pending, completed, cancelled) for a specific context.","parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"},"description":"Context ID"}],"responses":{"200":{"description":"Follow-up status summary counts"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/queue/due":{"get":{"tags":["Follow-Ups"],"summary":"Get due follow-ups","description":"Returns follow-up tasks that are due now or overdue and need to be processed.","responses":{"200":{"description":"List of due follow-up tasks"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/queue/requiring-approval":{"get":{"tags":["Follow-Ups"],"summary":"Get follow-ups requiring approval","description":"Returns follow-up tasks that need manager approval before being sent to customers. Requires admin, manager, or owner role.","responses":{"200":{"description":"Follow-ups pending approval"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/queue/internal-reminders":{"get":{"tags":["Follow-Ups"],"summary":"Get internal follow-up reminders","description":"Returns internal staff reminders (not customer-facing) from the follow-up queue.","responses":{"200":{"description":"Internal reminder tasks"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/queue/templates":{"get":{"tags":["Follow-Ups"],"summary":"Get follow-up templates","description":"Returns available follow-up message templates that can be used when creating or approving follow-up tasks.","responses":{"200":{"description":"List of follow-up templates"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/queue/rules":{"get":{"tags":["Follow-Ups"],"summary":"Get follow-up rules","description":"Returns the automation rules that determine when follow-up tasks are automatically created and scheduled.","responses":{"200":{"description":"List of follow-up automation rules"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/{id}/approve":{"post":{"tags":["Follow-Ups"],"summary":"Approve a follow-up task","description":"Approves a follow-up task that was pending manager review, queuing it for execution.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Follow-up approved"},"401":{"description":"Unauthorized"},"404":{"description":"Follow-up not found"}}}},"/api/v3/follow-ups/{id}/send":{"post":{"tags":["Follow-Ups"],"summary":"Send a follow-up task","description":"EXO-068/073. Delivers the follow-up's generated message through the conversation outbound pipeline (real SMS + thread persistence), then marks the follow-up sent. Replaces the old approve-as-send that flipped status without delivering anything.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Follow-up sent"},"401":{"description":"Unauthorized"},"404":{"description":"Follow-up not found"},"409":{"description":"Follow-up not in a sendable state"}}}},"/api/v3/follow-ups/{id}/cancel":{"post":{"tags":["Follow-Ups"],"summary":"Cancel a follow-up task","description":"Cancels a pending follow-up task with an optional reason, preventing it from being sent.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Follow-up cancelled"},"401":{"description":"Unauthorized"},"404":{"description":"Follow-up not found"}}}},"/api/v3/follow-ups/{id}/skip":{"post":{"tags":["Follow-Ups"],"summary":"Skip a follow-up task","description":"Marks a follow-up task as skipped without cancelling the entire sequence, allowing subsequent tasks to continue.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Follow-up skipped"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/{id}/status":{"put":{"tags":["Follow-Ups"],"summary":"Update follow-up status","description":"Updates the status of a follow-up task to a new value (e.g., pending, completed, failed).","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Status updated"},"400":{"description":"Invalid status value"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/{id}/responded":{"post":{"tags":["Follow-Ups"],"summary":"Mark follow-up as responded","description":"Records that the customer responded to a follow-up message, updating the status and stopping further follow-up chaining.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID"}],"responses":{"200":{"description":"Follow-up marked as responded"},"401":{"description":"Unauthorized"}}}},"/api/v3/follow-ups/{id}/chain":{"post":{"tags":["Follow-Ups"],"summary":"Chain a follow-up to create a subsequent task","description":"Creates a new follow-up task chained to the current one, scheduled after a configurable delay if there is no customer response.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Follow-up task ID to chain from"}],"responses":{"201":{"description":"Chained follow-up task created"},"401":{"description":"Unauthorized"},"404":{"description":"Follow-up not found"}}}},"/api/v3/gbp/auth":{"get":{"tags":["GBP"],"summary":"Get Google Business Profile OAuth URL","description":"Returns the Google OAuth 2.0 URL to initiate the Google Business Profile connection flow.","responses":{"200":{"description":"OAuth authorization URL"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/callback":{"get":{"tags":["GBP"],"summary":"Handle GBP OAuth callback","description":"Receives the authorization code from Google after the user grants Google Business Profile access and stores the tokens.","responses":{"302":{"description":"Redirect to settings page after successful connection"},"400":{"description":"Invalid authorization code or state"}}}},"/api/v3/gbp/status":{"get":{"tags":["GBP"],"summary":"Get GBP connection status","description":"Returns whether Google Business Profile is connected and the selected location details.","responses":{"200":{"description":"GBP connection status"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/disconnect":{"delete":{"tags":["GBP"],"summary":"Disconnect GBP integration","description":"Revokes Google Business Profile OAuth tokens and removes the integration. Requires integrations:delete permission.","responses":{"200":{"description":"GBP disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/reviews":{"get":{"tags":["GBP"],"summary":"List GBP reviews","description":"Returns paginated Google Business Profile reviews with rating, text, reviewer info, and reply status.","parameters":[{"in":"query","name":"page","schema":{"type":"integer"},"description":"Page number"},{"in":"query","name":"status","schema":{"type":"string","enum":["all","replied","unreplied"]},"description":"Filter by reply status"}],"responses":{"200":{"description":"Paginated reviews list"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/reviews/stats":{"get":{"tags":["GBP"],"summary":"Get review statistics","description":"Returns aggregate review statistics including average rating, total reviews, and reply rate.","responses":{"200":{"description":"Review statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/reviews/{reviewId}/reply":{"post":{"tags":["GBP"],"summary":"Reply to a GBP review","description":"Posts a reply to a Google Business Profile review. Requires integrations:update permission.","parameters":[{"in":"path","name":"reviewId","required":true,"schema":{"type":"string"},"description":"GBP review ID"}],"responses":{"200":{"description":"Reply posted"},"400":{"description":"Invalid reply content"},"401":{"description":"Unauthorized"}}},"delete":{"tags":["GBP"],"summary":"Delete a review reply","description":"Removes the owner reply from a Google Business Profile review. Requires integrations:update permission.","parameters":[{"in":"path","name":"reviewId","required":true,"schema":{"type":"string"},"description":"GBP review ID"}],"responses":{"200":{"description":"Reply deleted"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/reviews/{reviewId}/read":{"post":{"tags":["GBP"],"summary":"Mark review as read","description":"Marks a GBP review as read to dismiss it from the unread notifications count.","parameters":[{"in":"path","name":"reviewId","required":true,"schema":{"type":"string"},"description":"GBP review ID"}],"responses":{"200":{"description":"Review marked as read"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/reviews/sync":{"post":{"tags":["GBP"],"summary":"Sync reviews from GBP","description":"Manually triggers a sync of Google Business Profile reviews. Requires integrations:update permission.","responses":{"200":{"description":"Sync triggered"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/business-info":{"get":{"tags":["GBP"],"summary":"Get business information from GBP","description":"Returns the current business information cached from Google Business Profile including hours, address, and categories.","responses":{"200":{"description":"Business information"},"401":{"description":"Unauthorized"}}},"put":{"tags":["GBP"],"summary":"Update business information in GBP","description":"Pushes updated business information (hours, description, website) to Google Business Profile. Requires integrations:update permission.","responses":{"200":{"description":"Business info updated in GBP"},"400":{"description":"Invalid business info data"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/business-info/sync":{"post":{"tags":["GBP"],"summary":"Sync business info from GBP","description":"Pulls the latest business information from Google Business Profile and updates the local cache. Requires integrations:update permission.","responses":{"200":{"description":"Business info synced"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/booking-sources":{"get":{"tags":["GBP"],"summary":"Get booking sources from GBP","description":"Returns available third-party booking sources integrated with Google Business Profile. Requires integrations:read permission.","responses":{"200":{"description":"List of booking sources"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/place-actions":{"get":{"tags":["GBP"],"summary":"List GBP booking/action links","description":"Returns the profile's place action links (our own booking button plus any partner links like HouseCall Pro), with provider type. Requires integrations:read permission.","responses":{"200":{"description":"List of place action links"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/place-actions/booking/sync":{"post":{"tags":["GBP"],"summary":"Set Exoserva as the booking button","description":"Creates or updates the merchant-owned booking action link pointing at the tenant's Exoserva booking page and marks it preferred. Requires integrations:update permission.","responses":{"200":{"description":"Booking button synced"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/place-actions/{id}":{"delete":{"tags":["GBP"],"summary":"Remove a booking action link","description":"Deletes a merchant-owned booking action link by id. Partner links (e.g. HouseCall Pro) cannot be removed via the API. Requires integrations:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Place action link id (last resource-name segment)"}],"responses":{"200":{"description":"Booking link removed"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/analytics":{"get":{"tags":["GBP"],"summary":"Get GBP analytics time series","description":"Returns time-series Google Business Profile analytics (views, clicks, calls, direction requests) for the specified period.","parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Analytics period start date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Analytics period end date"}],"responses":{"200":{"description":"GBP analytics time series"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/analytics/summary":{"get":{"tags":["GBP"],"summary":"Get GBP analytics summary","description":"Returns aggregate GBP analytics totals for the specified period including total views, calls, and direction requests.","responses":{"200":{"description":"GBP analytics summary totals"},"401":{"description":"Unauthorized"}}}},"/api/v3/gbp/analytics/sync":{"post":{"tags":["GBP"],"summary":"Sync analytics from GBP","description":"Pulls the latest analytics data from Google Business Profile API and stores it locally for faster access. Requires integrations:update permission.","responses":{"200":{"description":"Analytics sync triggered"},"401":{"description":"Unauthorized"}}}},"/api/health":{"get":{"tags":["Health"],"summary":"Health check (scope depends on authentication)","description":"Public callers receive a minimal liveness payload (`status` +\n`timestamp`). Component-level diagnostics (database, Redis, queues,\nmigrations, external APIs) require admin/owner authentication, so\ninternal infrastructure detail is never exposed anonymously.","security":[{},{"bearerAuth":[]}],"responses":{"200":{"description":"System is healthy or degraded"},"503":{"description":"System is unhealthy"}}}},"/api/health/ready":{"get":{"tags":["Health"],"summary":"Readiness probe","security":[],"responses":{"200":{"description":"Ready to serve traffic"},"503":{"description":"Not ready"}}}},"/api/health/live":{"get":{"tags":["Health"],"summary":"Liveness probe","security":[],"responses":{"200":{"description":"Application is alive"}}}},"/api/health/version":{"get":{"tags":["Health"],"summary":"Build provenance (commit + builtAt)","description":"Returns build-time git sha and build timestamp injected at Docker\nbuild time. Used by Agent-4 (QA-Verify) to decide whether the\nstaging deploy includes a given PR (compare `builtAt` vs PR's\n`mergedAt`). Returns \"unknown\" for both fields when env vars\nare not set (e.g. local dev).","security":[],"responses":{"200":{"description":"Build provenance metadata"}}}},"/api/health/migrations":{"get":{"tags":["Health"],"summary":"Startup-migration completion (deploy gate)","description":"Coarse startup-migration state for the production deploy gate, which\npolls unauthenticated and therefore cannot read `components.migrations`\nfrom `/api/health` (redacted for anonymous callers). Reports a state\nplus counts only — migration names and failure reasons stay behind\nadmin/owner auth on the full health payload. `status` is one of `running` (poll again), `failed` (a step raised —\nabort the deploy), `incomplete` (nothing raised, but a one-shot step\nresolved without applying its change, so this instance is serving an\nunbuilt schema —, or `healthy` (every step applied).","security":[],"responses":{"200":{"description":"Migration state and counts"}}}},"/api/health/metrics":{"get":{"tags":["Health"],"summary":"System metrics","description":"Returns system status. Full metrics (memory, CPU, uptime) require admin/owner authentication.\nUnauthenticated requests receive only { status: \"ok\" }.","security":[{},{"bearerAuth":[]}],"responses":{"200":{"description":"System metrics (scope depends on authentication)"},"500":{"description":"Failed to get metrics"}}}},"/api/v3/integrations/hdsupply":{"get":{"tags":["Integrations"],"summary":"Get HD Supply integration settings","description":"Returns the current HD Supply integration configuration including auto-reorder mode and shipping preferences. The API key is masked in the response.","responses":{"200":{"description":"HD Supply integration settings or null if not configured"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Integrations"],"summary":"Save HD Supply integration settings","description":"Saves or updates the HD Supply API credentials and integration configuration using upsert. Passing an empty API key preserves the existing encrypted key.","responses":{"200":{"description":"Settings saved successfully"},"400":{"description":"Invalid settings data"},"401":{"description":"Unauthorized"}}},"delete":{"tags":["Integrations"],"summary":"Disconnect HD Supply integration","description":"Removes the HD Supply integration configuration and credentials from the tenant account.","responses":{"200":{"description":"Integration disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/integrations/hdsupply/test":{"post":{"tags":["Integrations"],"summary":"Test HD Supply connection","description":"Validates the provided HD Supply API key and account number credentials before saving. Returns success status based on credential format validation.","responses":{"200":{"description":"Connection test result with success boolean"},"401":{"description":"Unauthorized"}}}},"/api/v3/integrations/hdsupply/favorites":{"get":{"tags":["Integrations"],"summary":"List HD Supply favorite items","description":"Returns the tenant's saved HD Supply favorite products including SKU, name, and price for quick reordering.","responses":{"200":{"description":"List of favorite HD Supply items"},"401":{"description":"Unauthorized"}}}},"/api/v3/integrations/hdsupply/orders":{"get":{"tags":["Integrations"],"summary":"List recent HD Supply orders","description":"Returns the 50 most recent HD Supply orders for the tenant including PO number, status, total, and tracking number.","responses":{"200":{"description":"List of recent HD Supply orders"},"401":{"description":"Unauthorized"}}}},"/api/v3/integrations/hdsupply/reorder":{"post":{"tags":["Integrations"],"summary":"Reorder from a previous HD Supply order","description":"Creates a new pending HD Supply order by duplicating the items and total from a previous order, identified by orderId.","responses":{"200":{"description":"New order created with the duplicated order ID"},"400":{"description":"Missing orderId in request body"},"401":{"description":"Unauthorized"},"404":{"description":"Original order not found"}}}},"/api/v3/inventory/alerts":{"get":{"tags":["Inventory"],"summary":"Get pending low-stock alerts","description":"Returns all unresolved low-stock alerts for the tenant's inventory items that need attention.","responses":{"200":{"description":"List of pending low-stock alerts"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/alerts/{id}/acknowledge":{"post":{"tags":["Inventory"],"summary":"Acknowledge a low-stock alert","description":"Marks a low-stock alert as acknowledged by the current user, recording who reviewed it. Requires inventory:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Alert ID"}],"responses":{"200":{"description":"Alert acknowledged"},"401":{"description":"Unauthorized"},"404":{"description":"Alert not found"}}}},"/api/v3/inventory/alerts/{id}/resolve":{"post":{"tags":["Inventory"],"summary":"Resolve a low-stock alert","description":"Marks a low-stock alert as resolved with an optional note, indicating the stock issue has been addressed. Requires inventory:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Alert ID"}],"responses":{"200":{"description":"Alert resolved"},"400":{"description":"Invalid request data"},"401":{"description":"Unauthorized"},"404":{"description":"Alert not found"}}}},"/api/v3/inventory":{"get":{"tags":["Inventory"],"summary":"List inventory items","description":"Returns all inventory items for the tenant including current stock levels, SKU, category, and pricing.","responses":{"200":{"description":"List of inventory items"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Inventory"],"summary":"Create inventory item","description":"Creates a new inventory item for the tenant with SKU, pricing, and reorder threshold configuration.","responses":{"201":{"description":"Inventory item created"},"400":{"description":"Invalid item data"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/{id}":{"get":{"tags":["Inventory"],"summary":"Get inventory item by ID","description":"Returns full details of a specific inventory item including stock levels across all locations.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Inventory item ID"}],"responses":{"200":{"description":"Inventory item details"},"401":{"description":"Unauthorized"},"404":{"description":"Item not found"}}},"patch":{"tags":["Inventory"],"summary":"Update inventory item","description":"Partially updates an inventory item's properties such as name, SKU, price, or reorder threshold.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Inventory item ID"}],"responses":{"200":{"description":"Inventory item updated"},"400":{"description":"Invalid update data"},"401":{"description":"Unauthorized"},"404":{"description":"Item not found"}}},"delete":{"tags":["Inventory"],"summary":"Delete inventory item","description":"Permanently deletes an inventory item. Requires inventory:delete permission. This action is audited.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Inventory item ID"}],"responses":{"200":{"description":"Inventory item deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Item not found"}}}},"/api/v3/inventory/locations":{"get":{"tags":["Inventory"],"summary":"List inventory locations","description":"Returns all inventory storage locations for the tenant, optionally filtered by type (warehouse, truck, property, or vendor).","parameters":[{"in":"query","name":"type","schema":{"type":"string","enum":["warehouse","truck","property","vendor"]},"description":"Filter by location type"}],"responses":{"200":{"description":"List of inventory locations"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Inventory"],"summary":"Create inventory location","description":"Creates a new inventory storage location (warehouse, truck, property, or vendor) for tracking stock distribution.","responses":{"201":{"description":"Inventory location created"},"400":{"description":"Invalid location data"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/purchase-orders":{"get":{"tags":["Inventory"],"summary":"List purchase orders","description":"Returns a paginated list of inventory purchase orders for the tenant with status and supplier details.","responses":{"200":{"description":"List of purchase orders"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Inventory"],"summary":"Create purchase order","description":"Creates a new purchase order for restocking inventory. Requires inventory:create permission.","responses":{"201":{"description":"Purchase order created"},"400":{"description":"Invalid purchase order data"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/purchase-orders/{id}":{"get":{"tags":["Inventory"],"summary":"Get purchase order by ID","description":"Returns full details of a specific purchase order including all line items and their receipt status.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Purchase order ID"}],"responses":{"200":{"description":"Purchase order details"},"401":{"description":"Unauthorized"},"404":{"description":"Purchase order not found"}}}},"/api/v3/inventory/purchase-orders/{id}/line-items":{"post":{"tags":["Inventory"],"summary":"Add line item to purchase order","description":"Adds a new line item (product and quantity) to an existing purchase order. Requires inventory:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Purchase order ID"}],"responses":{"201":{"description":"Line item added to purchase order"},"400":{"description":"Invalid line item data"},"401":{"description":"Unauthorized"},"404":{"description":"Purchase order not found"}}}},"/api/v3/inventory/purchase-orders/{poId}/line-items/{lineItemId}/receive":{"post":{"tags":["Inventory"],"summary":"Receive a purchase order line item","description":"Marks a specific line item as received, updating inventory stock levels accordingly. Requires inventory:update permission.","parameters":[{"in":"path","name":"poId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Purchase order ID"},{"in":"path","name":"lineItemId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Line item ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"integer","minimum":1,"description":"Units actually received in this delivery"}}}}}},"responses":{"200":{"description":"Line item received and stock updated"},"401":{"description":"Unauthorized"},"404":{"description":"Purchase order or line item not found"}}}},"/api/v3/inventory/purchase-orders/{id}/approve":{"post":{"tags":["Inventory"],"summary":"Approve purchase order","description":"Approves a pending purchase order for fulfillment. Requires admin, manager, or owner role. This action is audited.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Purchase order ID"}],"responses":{"200":{"description":"Purchase order approved"},"401":{"description":"Unauthorized"},"404":{"description":"Purchase order not found"}}}},"/api/v3/inventory/stock":{"get":{"tags":["Inventory"],"summary":"List all stock levels","description":"Returns current stock quantities for all inventory items across all locations for the tenant.","responses":{"200":{"description":"Stock levels for all items"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/stock/{itemId}":{"get":{"tags":["Inventory"],"summary":"Get total stock for an item","description":"Returns the aggregate stock quantity for a specific inventory item summed across all storage locations.","parameters":[{"in":"path","name":"itemId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Inventory item ID"}],"responses":{"200":{"description":"Total stock quantity for the item"},"401":{"description":"Unauthorized"},"404":{"description":"Item not found"}}}},"/api/v3/inventory/stock/{itemId}/{locationId}":{"get":{"tags":["Inventory"],"summary":"Get stock at a specific location","description":"Returns the stock quantity for a specific inventory item at a specific storage location.","parameters":[{"in":"path","name":"itemId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Inventory item ID"},{"in":"path","name":"locationId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Storage location ID"}],"responses":{"200":{"description":"Stock quantity at the specified location"},"401":{"description":"Unauthorized"},"404":{"description":"Item or location not found"}}}},"/api/v3/inventory/stock/reserve":{"post":{"tags":["Inventory"],"summary":"Reserve stock","description":"Reserves a quantity of an inventory item for a specific job or work order, reducing available stock. Requires inventory:update permission.","responses":{"200":{"description":"Stock reserved successfully"},"400":{"description":"Invalid reservation data or insufficient stock"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/stock/release":{"post":{"tags":["Inventory"],"summary":"Release reserved stock","description":"Releases previously reserved stock back to available inventory, typically called when a job is cancelled or stock reservation is no longer needed. Requires inventory:update permission.","responses":{"200":{"description":"Stock reservation released"},"400":{"description":"Invalid release data"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/transactions":{"post":{"tags":["Inventory"],"summary":"Record an inventory transaction","description":"Records a stock movement transaction (receipt, issue, transfer, adjustment) and updates stock levels accordingly.","responses":{"201":{"description":"Transaction recorded and stock levels updated"},"400":{"description":"Invalid transaction data"},"401":{"description":"Unauthorized"}}}},"/api/v3/inventory/transactions/{itemId}":{"get":{"tags":["Inventory"],"summary":"Get transaction history for an item","description":"Returns the transaction history for a specific inventory item showing all stock movements with timestamps and user attribution.","parameters":[{"in":"path","name":"itemId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Inventory item ID"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of transactions to return"}],"responses":{"200":{"description":"Transaction history for the item"},"401":{"description":"Unauthorized"},"404":{"description":"Item not found"}}}},"/api/v3/leases/{id}/renew":{"post":{"tags":["Leases"],"summary":"Renew a lease","description":"Renews an existing lease with updated terms and a new end date. Requires leases:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Lease ID"}],"responses":{"200":{"description":"Lease renewed"},"400":{"description":"Invalid renewal data"},"401":{"description":"Unauthorized"},"404":{"description":"Lease not found"}}}},"/api/v3/leases/{id}/terminate":{"post":{"tags":["Leases"],"summary":"Terminate a lease","description":"Terminates an active lease early with an optional termination reason and effective date. Requires leases:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Lease ID"}],"responses":{"200":{"description":"Lease terminated"},"400":{"description":"Invalid termination data"},"401":{"description":"Unauthorized"},"404":{"description":"Lease not found"}}}},"/api/v3/leases/{id}/send-reminder":{"post":{"tags":["Leases"],"summary":"Send lease reminder","description":"Sends a reminder notification to the tenant about an upcoming lease renewal, payment due, or expiration. Requires leases:update permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Lease ID"}],"responses":{"200":{"description":"Reminder sent"},"401":{"description":"Unauthorized"},"404":{"description":"Lease not found"}}}},"/api/v3/leases/stats":{"get":{"tags":["Leases"],"summary":"Get lease statistics","description":"Returns aggregate lease statistics including active count, expiring soon, and total monthly revenue. Requires leases:read permission.","responses":{"200":{"description":"Lease statistics summary"},"401":{"description":"Unauthorized"}}}},"/api/v3/leases":{"get":{"tags":["Leases"],"summary":"List leases with filtering","description":"Returns a filtered list of leases for the tenant. Supports filtering by status, property, and date range. Requires leases:read permission.","responses":{"200":{"description":"List of leases"},"401":{"description":"Unauthorized"}}}},"/api/v3/leases/{id}":{"get":{"tags":["Leases"],"summary":"Get lease by ID","description":"Returns full details of a specific lease including terms, payment history, and associated property. Requires leases:read permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Lease ID"}],"responses":{"200":{"description":"Lease details"},"401":{"description":"Unauthorized"},"404":{"description":"Lease not found"}}}},"/api/v3/invoices/{id}/line-items":{"get":{"tags":["Invoices"],"summary":"Get invoice line items","description":"Returns all line items for a specific invoice including quantities, unit prices, and descriptions. Requires page:invoices view permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Invoice ID"}],"responses":{"200":{"description":"List of invoice line items"},"401":{"description":"Unauthorized"},"404":{"description":"Invoice not found"}}}},"/api/v3/invoices/{id}/checkout":{"post":{"tags":["Invoices"],"summary":"Initiate Stripe checkout session for invoice payment","description":"Creates a Stripe checkout session for the customer to pay the invoice online. Subject to per-user rate limiting (20 req/min) and idempotency to prevent duplicate charges. Requires page:invoices edit permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Invoice ID"}],"responses":{"200":{"description":"Stripe checkout session URL"},"400":{"description":"Invalid invoice or payment data"},"401":{"description":"Unauthorized"},"404":{"description":"Invoice not found"},"429":{"description":"Too many requests"}}}},"/api/v3/invoices/{id}/send-reminder":{"post":{"tags":["Invoices"],"summary":"Send payment reminder for an invoice","description":"Sends a payment reminder to the customer via email or SMS for an outstanding invoice. Subject to per-user rate limiting (20 req/min) to prevent messaging abuse. Requires page:invoices edit permission.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Invoice ID"}],"responses":{"200":{"description":"Payment reminder sent"},"400":{"description":"Invalid reminder data"},"401":{"description":"Unauthorized"},"404":{"description":"Invoice not found"},"429":{"description":"Too many requests"}}}},"/api/v3/invoices/reports/aged-receivables":{"get":{"tags":["Invoices"],"summary":"Get aged receivables report","description":"Returns aging buckets (0-30, 31-60, 61-90, 90+ days) for outstanding invoices to support collections and cash-flow analysis.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"account_id","schema":{"type":"string","format":"uuid"},"description":"Filter report to a specific account"}],"responses":{"200":{"description":"Aged receivables report data"},"400":{"description":"Invalid query parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/invoices/stats/{account_id}":{"get":{"tags":["Invoices"],"summary":"Get account invoice stats","description":"Returns aggregate invoice statistics (total, paid, outstanding, overdue) for the specified customer account.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"account_id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"responses":{"200":{"description":"Invoice statistics for the account"},"400":{"description":"Invalid account_id format"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Account not found"}}}},"/api/v3/maintenance-schedules":{"post":{"tags":["Maintenance Schedules"],"summary":"Create maintenance schedule","description":"Creates a new recurring or one-time preventive maintenance schedule for an asset.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Maintenance schedule created"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"tags":["Maintenance Schedules"],"summary":"List maintenance schedules","description":"Returns a paginated list of maintenance schedules for the tenant, with optional filtering by asset or status.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of maintenance schedules"},"400":{"description":"Invalid query parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-schedules/{id}":{"get":{"tags":["Maintenance Schedules"],"summary":"Get maintenance schedule by ID","description":"Returns the full details of a single maintenance schedule including associated work orders.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Maintenance schedule details"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Schedule not found"}}},"delete":{"tags":["Maintenance Schedules"],"summary":"Delete maintenance schedule","description":"Soft-deletes a maintenance schedule (sets is_active=false). The schedule is no longer surfaced in lists or upcoming queries, and future occurrences stop generating. Existing execution history is retained.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Schedule deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Caller lacks maintenance:delete permission"},"404":{"description":"Schedule not found"}}}},"/api/v3/maintenance-schedules/upcoming/{days}":{"get":{"tags":["Maintenance Schedules"],"summary":"Get upcoming maintenance","description":"Retrieve maintenance schedules due within the specified number of days to support proactive planning.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"days","required":true,"schema":{"type":"integer","minimum":1},"description":"Number of days ahead to look for upcoming maintenance"}],"responses":{"200":{"description":"List of upcoming maintenance schedules"},"400":{"description":"Invalid days parameter"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-schedules/overdue/list":{"get":{"tags":["Maintenance Schedules"],"summary":"Get overdue maintenance","description":"Retrieve all overdue maintenance schedules for the tenant to prioritize corrective action.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of overdue maintenance schedules"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-schedules/process/manual":{"post":{"tags":["Maintenance Schedules"],"summary":"Manual PM processing","description":"Manually triggers preventive maintenance status evaluation and returns a count of overdue schedules for immediate action.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"PM processing completed with overdue count"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-schedules/metrics/compliance":{"get":{"tags":["Maintenance Schedules"],"summary":"Get tenant compliance metrics","description":"Returns aggregate compliance rate, total schedules, scheduled count, and overdue count for the tenant's preventive maintenance program.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Compliance metrics summary"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-schedules/compliance/{assetId}":{"get":{"tags":["Maintenance Schedules"],"summary":"Get asset compliance metrics","description":"Returns compliance history and current status for a specific asset's maintenance schedules.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"assetId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Asset compliance data"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Asset not found"}}}},"/api/v3/maintenance-schedules/{id}/link-work-order":{"post":{"tags":["Maintenance Schedules"],"summary":"Link work order to maintenance schedule","description":"Associates an existing work order with a maintenance schedule to track completion of preventive maintenance tasks.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Work order linked successfully"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Schedule not found"}}}},"/api/v3/maintenance-schedules/{id}/complete":{"post":{"tags":["Maintenance Schedules"],"summary":"Mark maintenance schedule as complete","description":"Marks a maintenance schedule as completed and advances the next due date according to its recurrence rule.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Schedule marked as complete"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Schedule not found"}}}},"/api/v3/maintenance-schedules/{id}/skip":{"post":{"tags":["Maintenance Schedules"],"summary":"Skip a maintenance schedule","description":"Skips the current maintenance occurrence with a reason and advances the schedule to the next due date.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Schedule occurrence skipped"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Schedule not found"}}}},"/api/v3/maintenance-schedules/{id}/cancel":{"post":{"tags":["Maintenance Schedules"],"summary":"Cancel a maintenance schedule","description":"Permanently cancels a maintenance schedule, stopping all future occurrences for the associated asset.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Schedule cancelled"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Schedule not found"}}}},"/api/v3/maintenance-schedules/{id}/notifications":{"patch":{"tags":["Maintenance Schedules"],"summary":"Update maintenance schedule notification settings","description":"Configures reminder and alert notification preferences for a maintenance schedule.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Notification settings updated"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Schedule not found"}}}},"/api/v3/maintenance-templates/{id}/status":{"post":{"tags":["Maintenance Templates"],"summary":"Set template status","description":"Updates the active/inactive status of a maintenance template to control whether it can be used for new schedules.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Template status updated"},"400":{"description":"Invalid status value"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Template not found"}}}},"/api/v3/maintenance-templates/{id}/archive":{"post":{"tags":["Maintenance Templates"],"summary":"Archive maintenance template","description":"Soft-archives a maintenance template, removing it from active use while preserving history on existing schedules.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Template archived successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Template not found"}}}},"/api/v3/maintenance-templates":{"post":{"tags":["Maintenance Templates"],"summary":"Create maintenance template","description":"Creates a reusable maintenance template defining tasks, intervals, and checklists for a class of assets.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Template created"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"tags":["Maintenance Templates"],"summary":"List maintenance templates","description":"Returns all maintenance templates for the tenant with optional filtering by status or asset type.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of maintenance templates"},"400":{"description":"Invalid query parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-templates/by-asset-type/{assetType}":{"get":{"tags":["Maintenance Templates"],"summary":"Get maintenance templates by asset type","description":"Retrieves all active maintenance templates applicable to the specified asset type for schedule creation.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"assetType","required":true,"schema":{"type":"string"},"description":"Asset type identifier (e.g. hvac, plumbing, electrical)"}],"responses":{"200":{"description":"Templates matching the asset type"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/maintenance-templates/{id}":{"get":{"tags":["Maintenance Templates"],"summary":"Get maintenance template by ID","description":"Returns full details of a maintenance template including all tasks and checklist items.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Maintenance template details"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Template not found"}}},"patch":{"tags":["Maintenance Templates"],"summary":"Update maintenance template","description":"Updates the tasks, intervals, or metadata of an existing maintenance template.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Template updated"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Template not found"}}}},"/api/mobile/analytics":{"get":{"tags":["Mobile"],"summary":"Get technician analytics dashboard","description":"Returns a full performance dashboard for the authenticated technician including job counts, revenue, and ratings for a given period.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"}},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Technician analytics dashboard data"},"400":{"description":"Invalid query parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/analytics/summary":{"get":{"tags":["Mobile"],"summary":"Get quick performance summary","description":"Returns a lightweight performance summary (jobs today, weekly revenue, rating) for display in the mobile app header.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Quick performance summary"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/analytics/trend":{"get":{"tags":["Mobile"],"summary":"Get weekly performance trend","description":"Returns day-by-day performance data for the past N weeks to power trend charts in the mobile analytics dashboard.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Weekly trend data"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/login":{"post":{"tags":["Mobile"],"summary":"Mobile login with device registration","description":"Authenticates a technician and registers their device, returning short-lived access and long-lived refresh tokens for offline use.","responses":{"200":{"description":"Authentication tokens and user profile"},"400":{"description":"Invalid credentials or body"},"401":{"description":"Invalid email or password"}}}},"/api/mobile/refresh":{"post":{"tags":["Mobile"],"summary":"Refresh mobile auth tokens","description":"Exchanges a valid device refresh token for a new access token without requiring re-login.","responses":{"200":{"description":"New access token"},"400":{"description":"Invalid request body"},"401":{"description":"Invalid or expired refresh token"}}}},"/api/mobile/devices":{"get":{"tags":["Mobile"],"summary":"Get registered devices for current user","description":"Lists all devices the current user has registered, including last-seen timestamps.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of registered devices"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/revoke":{"post":{"tags":["Mobile"],"summary":"Revoke a specific device","description":"Invalidates all tokens for the specified device, effectively logging it out remotely.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Device revoked"},"400":{"description":"Invalid device ID"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/revoke-all":{"post":{"tags":["Mobile"],"summary":"Revoke all devices (logout everywhere)","description":"Invalidates all active device tokens for the current user, forcing re-login on all devices.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All devices revoked"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/push-token":{"put":{"tags":["Mobile"],"summary":"Update push notification token","description":"Registers or updates the FCM/APNs push token for the current device so the server can send push notifications.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Push token updated"},"400":{"description":"Invalid push token"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/dispatches/respond":{"post":{"tags":["Mobile"],"summary":"Accept or decline a dispatch","description":"One-tap accept or decline response for a dispatch assignment, updating the job status accordingly.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Dispatch response recorded"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/location":{"post":{"tags":["Mobile"],"summary":"Update technician location","description":"Posts the technician's current GPS coordinates for real-time tracking on the dispatch board.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Location recorded"},"400":{"description":"Invalid coordinates"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/navigation/start":{"post":{"tags":["Mobile"],"summary":"Start navigation to job","description":"Records that a technician has started navigating to a job site, updating job status to en_route.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Navigation started"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/history":{"get":{"tags":["Mobile"],"summary":"Get dispatch response history","description":"Returns a paginated history of dispatch accept/decline responses for the authenticated technician.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Dispatch response history"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/threads":{"get":{"tags":["Mobile"],"summary":"List messaging threads","description":"Returns all messaging threads (direct and job-linked) for the authenticated user, ordered by most recent activity.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of messaging threads"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/threads/direct":{"post":{"tags":["Mobile"],"summary":"Start a direct message thread","description":"Creates or returns an existing direct messaging thread between two staff members.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Thread created or existing thread returned"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/threads/job":{"post":{"tags":["Mobile"],"summary":"Create a job message thread","description":"Creates a group messaging thread linked to a specific job for team collaboration on that work order.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Job thread created"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/threads/{id}":{"get":{"tags":["Mobile"],"summary":"Get a message thread by ID","description":"Returns thread metadata and participant list for a specific messaging thread.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Thread details"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Thread not found"}}}},"/api/mobile/messages/threads/{id}/messages":{"get":{"tags":["Mobile"],"summary":"List messages in a thread","description":"Returns paginated messages within a thread, newest-first, for display in the mobile chat view.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Paginated messages"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Thread not found"}}},"post":{"tags":["Mobile"],"summary":"Send a message in a thread","description":"Sends a text or attachment message to all participants in the thread.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Message sent"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Thread not found"}}}},"/api/mobile/messages/threads/{id}/read":{"post":{"tags":["Mobile"],"summary":"Mark thread as read","description":"Marks all messages in the thread as read for the current user, resetting the unread count.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Thread marked as read"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/threads/{id}/archive":{"delete":{"tags":["Mobile"],"summary":"Archive a message thread","description":"Hides the thread from the user's inbox without deleting it for other participants.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Thread archived"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/messages/{messageId}":{"delete":{"tags":["Mobile"],"summary":"Delete a message","description":"Soft-deletes a message sent by the current user, replacing its content with a deletion notice.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"messageId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Message deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Message not found"}}}},"/api/mobile/messages/unread":{"get":{"tags":["Mobile"],"summary":"Get total unread message count","description":"Returns the total number of unread messages across all threads for the badge count in the mobile app.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Total unread count"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/messages/unread/by-thread":{"get":{"tags":["Mobile"],"summary":"Get unread count per thread","description":"Returns a map of thread IDs to unread message counts for showing per-thread badges in the thread list.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Per-thread unread counts"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/sync/changes":{"get":{"tags":["Mobile"],"summary":"Get delta sync changes","description":"Returns all server-side data changes since the client's last sync timestamp, enabling efficient offline-first synchronization.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"since","required":true,"schema":{"type":"string","format":"date-time"},"description":"ISO timestamp of last successful sync"}],"responses":{"200":{"description":"Delta changes since last sync"},"400":{"description":"Invalid query parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/sync/push":{"post":{"tags":["Mobile"],"summary":"Push offline mutations to server","description":"Applies a batch of locally queued mutations (creates, updates) to the server after reconnecting from offline mode.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Mutations applied with per-item results"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/sync/stats":{"get":{"tags":["Mobile"],"summary":"Get sync statistics","description":"Returns sync health metrics including last sync time, pending mutation count, and conflict count for diagnostics.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sync statistics"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/sync/mutations":{"get":{"tags":["Mobile"],"summary":"List pending sync mutations","description":"Lists unprocessed mutation queue entries for admin review and debugging of sync issues.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Pending mutations"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/sync/conflicts":{"get":{"tags":["Mobile"],"summary":"List sync conflicts","description":"Lists data conflicts that arose from concurrent offline edits and require manual resolution.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of sync conflicts"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/mobile/sync/process":{"post":{"tags":["Mobile"],"summary":"Trigger sync processing","description":"Manually triggers the sync processing pipeline to flush pending mutations immediately without waiting for the scheduled job.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sync processing triggered"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules":{"get":{"tags":["Modules"],"summary":"List all system modules","description":"Returns the complete catalog of platform modules with descriptions, requirements, and pricing tier availability.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"category","schema":{"type":"string","enum":["core","operations","crm","property","financial","ai","analytics","integrations"]}},{"in":"query","name":"tier","schema":{"type":"string","enum":["wedge","growth","scale","enterprise"]}}],"responses":{"200":{"description":"Module catalog"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules/tenant":{"get":{"tags":["Modules"],"summary":"Get modules for current tenant with enabled status","description":"Returns all modules with their enabled/disabled status for the current tenant to drive feature flags on the frontend.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All modules with enabled status for tenant"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules/enabled":{"get":{"tags":["Modules"],"summary":"Get enabled modules for current tenant","description":"Returns only the modules currently active for the tenant, used for lightweight permission checks at app startup.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of enabled modules"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules/available":{"get":{"tags":["Modules"],"summary":"Get modules available for tenant tier","description":"Returns modules the tenant can enable based on their current subscription tier, used to populate the module store.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Modules available for the tenant's tier"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules/stats":{"get":{"tags":["Modules"],"summary":"Get module usage statistics","description":"Returns aggregate usage counts and last-accessed timestamps per module for admin reporting.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Module usage statistics"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules/check/{code}":{"get":{"tags":["Modules"],"summary":"Check if a module is enabled for tenant","description":"Returns a boolean indicating whether a specific module is currently enabled, used for lightweight feature-flag checks.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Module enabled status"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/modules/{code}":{"get":{"tags":["Modules"],"summary":"Get module by code","description":"Returns the full module definition including description, features list, and configuration requirements.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Module definition"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Module not found"}}}},"/api/v3/modules/{code}/enable":{"post":{"tags":["Modules"],"summary":"Enable a module for tenant","description":"Activates a platform module for the tenant, making its features and routes accessible to authorized users.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Module enabled"},"400":{"description":"Module not available for tenant tier"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Module not found"}}}},"/api/v3/modules/{code}/disable":{"post":{"tags":["Modules"],"summary":"Disable a module for tenant","description":"Deactivates a module for the tenant, hiding its functionality while preserving any stored data.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Module disabled"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Module not found"}}}},"/api/v3/notifications/unread-count":{"get":{"tags":["Notifications"],"summary":"Get unread notification count","description":"Get count of unacknowledged notifications for current tenant","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Unread count"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/notifications/priority/{priority}/count":{"get":{"tags":["Notifications"],"summary":"Get unread count by priority","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"priority","required":true,"schema":{"type":"string","enum":["low","normal","high","urgent"]}}],"responses":{"200":{"description":"Count of unread notifications by priority"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/notifications/stream/ticket":{"post":{"tags":["Notifications"],"summary":"Issue single-use SSE connection ticket","description":"Issues a short-lived single-use ticket that can be used to establish an SSE connection without passing a Bearer token in the URL.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"SSE connection ticket"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/notifications/stream":{"get":{"tags":["Notifications"],"summary":"Establish SSE connection for real-time notifications","description":"Opens a Server-Sent Events stream that pushes real-time notifications to the client; authenticate via ticket or Bearer token query param.","parameters":[{"in":"query","name":"ticket","schema":{"type":"string"},"description":"Single-use SSE connection ticket"},{"in":"query","name":"token","schema":{"type":"string"},"description":"Bearer token (alternative to ticket)"}],"responses":{"200":{"description":"SSE event stream (text/event-stream)"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/notifications/stream/stats":{"get":{"tags":["Notifications"],"summary":"Get SSE stream statistics","description":"Returns current SSE connection count and throughput metrics for operations monitoring.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"SSE stream statistics"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/notifications/web-token":{"post":{"tags":["Notifications"],"summary":"Register or refresh an FCM push token for the current user (web or native)","description":"Frontend calls this after notification permission is granted and the FCM SDK yields a token. The body carries the opaque FCM token, an optional `userAgent` for diagnostics, an optional `platform` (`web` default, or `ios`/`android` for the Capacitor shell), and an optional `deviceName` override (e.g. `iPhone17,1`) so the row carries the actual model label instead of the generic platform default. The backend stores it in `mobile_devices` with the matching `device_type`, deriving a stable synthetic `device_id` from `sha256(token + user_id)` so re-calls upsert in place. Token is treated as a confidential value and never logged.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":20,"maxLength":4096},"userAgent":{"type":"string","maxLength":2048,"nullable":true},"platform":{"type":"string","enum":["web","ios","android"]},"deviceName":{"type":"string","minLength":1,"maxLength":255}}}}}},"responses":{"200":{"description":"Token registered or refreshed"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}},"delete":{"tags":["Notifications"],"summary":"Unregister an FCM push token (logout / consent revoke)","description":"Frontend calls this on logout or when the user revokes push permission. Body re-supplies the same FCM token (and `platform` for native callers); we re-derive the synthetic device_id and revoke the row. Idempotent — repeated calls are safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":20,"maxLength":4096},"platform":{"type":"string","enum":["web","ios","android"]}}}}}},"responses":{"200":{"description":"Token revoked (or already absent)"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/onboarding/status":{"get":{"tags":["Onboarding"],"summary":"Get current onboarding progress","description":"Returns the tenant's onboarding completion status, current step, and any partially answered wizard responses.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Onboarding progress and current step"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/onboarding/step":{"post":{"tags":["Onboarding"],"summary":"Save onboarding step progress","description":"Persists the user's answer for a single wizard step, allowing the wizard to resume from the last completed step.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Step saved"},"400":{"description":"Invalid step data"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/onboarding/complete":{"post":{"tags":["Onboarding"],"summary":"Complete onboarding and set selected subscription tier","description":"Finalizes the onboarding wizard, sets the tenant's subscription tier, and unlocks the full platform.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Onboarding completed and tier activated"},"400":{"description":"Invalid tier or incomplete wizard"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/onboarding/tiers":{"get":{"tags":["Onboarding"],"summary":"Get all available subscription tiers and pricing","description":"Returns all subscription tiers with features, limits, and pricing for display on the onboarding tier selection screen.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Available tiers and pricing"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/onboarding/recommend":{"post":{"tags":["Onboarding"],"summary":"Get tier recommendation based on answers without saving","description":"Runs the tier recommendation algorithm against the provided wizard answers and returns a suggested tier without persisting anything.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Recommended tier with justification"},"400":{"description":"Invalid answers payload"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/organizations":{"get":{"tags":["Organizations"],"summary":"List organizations","description":"Returns all organizations within the tenant's hierarchy for display in the org management UI.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of organizations"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Organizations"],"summary":"Create organization","description":"Creates a new organization unit within the tenant hierarchy, optionally nested under a parent organization.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Organization created"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/organizations/hierarchy":{"get":{"tags":["Organizations"],"summary":"Get organization hierarchy tree","description":"Returns the tenant's organization chart as a nested tree structure for org-chart visualizations and parent-child assignment.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Organization hierarchy tree"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/organizations/{id}":{"patch":{"tags":["Organizations"],"summary":"Update organization","description":"Updates the name, description, or metadata of an existing organization.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Organization updated"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Organization not found"}}},"delete":{"tags":["Organizations"],"summary":"Delete organization","description":"Deletes an organization unit; fails if it has child organizations or assigned users.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Organization deleted"},"400":{"description":"Organization has children or assigned users"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Organization not found"}}}},"/api/v3/organizations/{id}/move":{"patch":{"tags":["Organizations"],"summary":"Move organization in hierarchy","description":"Reassigns an organization to a different parent, restructuring the org chart while preserving its children.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Organization moved"},"400":{"description":"Invalid parent or circular reference"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Organization not found"}}}},"/api/payments/stripe/process":{"post":{"tags":["Payments"],"summary":"Process Stripe payment","description":"Charges a Stripe payment method and records the transaction result, protected by idempotency to prevent double-charges.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Payment processed"},"400":{"description":"Invalid request body or card declined"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/create-payment-intent":{"post":{"tags":["Payments"],"summary":"Create Stripe payment intent","description":"Creates a Stripe PaymentIntent and returns the client_secret for completing payment in the browser or mobile app.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"PaymentIntent client secret"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/terminal/connection-token":{"post":{"tags":["Payments"],"summary":"Create a Stripe Terminal connection token (in-person / Tap to Pay)","description":"Mints a Terminal connection token scoped to the tenant's connected account. Gated behind TERMINAL_TAP_TO_PAY_ENABLED. Requires an active Stripe Connect account.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection token secret"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/terminal/location":{"post":{"tags":["Payments"],"summary":"Create a Stripe Terminal location on the connected account","description":"Creates a Terminal Location (required for Tap to Pay on iPhone) on the tenant's connected account. Gated behind TERMINAL_TAP_TO_PAY_ENABLED.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Location id"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/terminal/payment-intent":{"post":{"tags":["Payments"],"summary":"Create a card-present PaymentIntent (in-person / Tap to Pay)","description":"Creates a card-present PaymentIntent as a direct charge on the tenant's connected account with the platform application fee. Gated behind TERMINAL_TAP_TO_PAY_ENABLED. Requires an active Stripe Connect account.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"PaymentIntent client secret"},"400":{"description":"Invalid request body or invoice not payable"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/confirm-invoice-payment":{"post":{"tags":["Payments"],"summary":"Confirm invoice payment","description":"Confirms that a Stripe payment for an invoice was successful and updates the invoice status to paid.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Invoice payment confirmed"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/record-invoice-payment":{"post":{"tags":["Payments"],"summary":"Record invoice payment","description":"Records a non-Stripe payment (cash, check, bank transfer) against an invoice without processing through a payment gateway.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Payment recorded"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/payments/{id}/apply":{"post":{"tags":["Payments"],"summary":"Apply payment to invoice","description":"Applies an existing unapplied payment balance to a specific invoice, reducing the invoice's outstanding amount.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment applied to invoice"},"400":{"description":"Insufficient payment balance"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Payment not found"}}}},"/api/payments/{id}/complete":{"post":{"tags":["Payments"],"summary":"Complete payment","description":"Marks a pending payment as completed, finalizing the transaction in the accounting ledger.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment completed"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Payment not found"}}}},"/api/payments/{id}/fail":{"post":{"tags":["Payments"],"summary":"Mark payment as failed","description":"Marks a payment as failed (e.g. after a bounced check) and reverses any applied invoice credits.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment marked as failed"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Payment not found"}}}},"/api/v3/payments":{"post":{"tags":["Payments"],"summary":"Create new payment","description":"Creates a payment record for a customer and optionally applies it to outstanding invoices.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Payment created"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"tags":["Payments"],"summary":"List payments","description":"Returns a paginated list of payments for the tenant, with optional filtering by customer, date range, or status.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of payments"},"400":{"description":"Invalid query parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/payments/{id}":{"get":{"tags":["Payments"],"summary":"Get payment by ID","description":"Returns full payment details including applied invoices, method, and transaction metadata.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment details"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Payment not found"}}},"patch":{"tags":["Payments"],"summary":"Update payment","description":"Updates editable fields (notes, reference number) on an existing payment record.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment updated"},"400":{"description":"Invalid request body"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Payment not found"}}}},"/api/v3/payments/{id}/applications":{"get":{"tags":["Payments"],"summary":"Get payment applications","description":"Returns the list of invoices this payment has been applied to and the amounts applied to each.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of invoice applications"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Payment not found"}}}},"/api/v3/phone-provisioning/compliance/bundle":{"post":{"tags":["Phone Provisioning"],"summary":"Create compliance bundle","description":"Submits business information to Twilio to create a regulatory compliance bundle required for phone number provisioning.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Compliance bundle created"},"400":{"description":"Invalid business information"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/compliance/bundle/{bundleSid}":{"get":{"tags":["Phone Provisioning"],"summary":"Get compliance bundle status","description":"Returns the current review status of a Twilio regulatory compliance bundle.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleSid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bundle status"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Bundle not found"}}}},"/api/v3/phone-provisioning/compliance/bundles":{"get":{"tags":["Phone Provisioning"],"summary":"List compliance bundles","description":"Returns all Twilio compliance bundles associated with the tenant.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of compliance bundles"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/compliance/a2p/brand":{"post":{"tags":["Phone Provisioning"],"summary":"Register A2P brand","description":"Registers the business as an A2P 10DLC brand with The Campaign Registry for SMS compliance.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Brand registered"},"400":{"description":"Invalid business information"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/compliance/a2p/campaign":{"post":{"tags":["Phone Provisioning"],"summary":"Create A2P campaign","description":"Registers an SMS use-case campaign (e.g. appointment reminders, notifications) required before sending A2P business messages.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Campaign created"},"400":{"description":"Invalid campaign data"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/compliance/a2p":{"get":{"tags":["Phone Provisioning"],"summary":"Get A2P status","description":"Returns the current A2P brand and campaign registration status for the tenant.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"A2P registration status"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/status":{"get":{"tags":["Phone Provisioning"],"summary":"Get ISV setup status","description":"Returns the current Twilio ISV (Independent Software Vendor) onboarding completion status for the tenant.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"ISV setup status"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/refresh":{"post":{"tags":["Phone Provisioning"],"summary":"Force a live A2P status re-sync","description":"Re-polls the tenant's A2P brand + campaign from Twilio and applies the status writeback (which re-fires the event-driven campaign creation when a brand is approved). Returns the freshened status.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Freshened ISV setup status"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/onboarding":{"post":{"tags":["Phone Provisioning"],"summary":"Run full ISV onboarding workflow","description":"Executes the complete Twilio ISV onboarding sequence (sub-account, messaging service, A2P brand, campaign, phone number) in a single call.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"ISV onboarding completed"},"400":{"description":"Invalid business information"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/subaccount":{"post":{"tags":["Phone Provisioning"],"summary":"Create Twilio sub-account","description":"Creates a Twilio sub-account under the platform master account to isolate the tenant's telephony resources.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Sub-account created"},"400":{"description":"Invalid request"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/messaging-service":{"post":{"tags":["Phone Provisioning"],"summary":"Create messaging service","description":"Creates a Twilio Messaging Service for the tenant's sub-account to manage SMS sending pools.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Messaging service created"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/a2p-brand":{"post":{"tags":["Phone Provisioning"],"summary":"Register A2P brand","description":"Registers the tenant's business as an A2P 10DLC brand on the ISV sub-account.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Brand registered"},"400":{"description":"Invalid business information"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/a2p-campaign":{"post":{"tags":["Phone Provisioning"],"summary":"Create A2P campaign","description":"Creates an A2P messaging campaign on the ISV sub-account for a specific SMS use case.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Campaign created"},"400":{"description":"Invalid campaign data"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/phone-number":{"post":{"tags":["Phone Provisioning"],"summary":"Purchase phone number","description":"Purchases a Twilio phone number and assigns it to the tenant's messaging service.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Phone number purchased"},"400":{"description":"Invalid request or number unavailable"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/register":{"post":{"tags":["Phone Provisioning"],"summary":"Self-serve A2P registration for an existing tenant","description":"Persists the tenant's A2P business info and enqueues the auto-provision job (sub-account, messaging service, brand, campaign, phone number). Legal name and compliance contact are derived from the tenant + owner.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Registration accepted; provisioning enqueued"},"400":{"description":"Invalid business information"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/isv/numbers/{sid}/primary":{"post":{"tags":["Phone Provisioning"],"summary":"Make a number the tenant's primary","description":"Re-points the tenant's primary (outbound / default caller-id) flag to the given number.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string"},"description":"Twilio IncomingPhoneNumber SID (PN…)"}],"responses":{"200":{"description":"Primary number updated"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Number not found for tenant"}}}},"/api/v3/phone-provisioning/isv/numbers/{sid}":{"delete":{"tags":["Phone Provisioning"],"summary":"Release a number back to Twilio","description":"Releases the number from the tenant's sub-account, stopping billing. Blocked (409) when the number is the primary or the last SMS-capable number.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string"},"description":"Twilio IncomingPhoneNumber SID (PN…)"}],"responses":{"200":{"description":"Number released"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Number not found for tenant"},"409":{"description":"Number is primary or the last SMS-capable number"}}}},"/api/v3/phone-provisioning/porting/check":{"post":{"tags":["Phone Provisioning"],"summary":"Check if number can be ported","description":"Verifies whether a phone number is portable and estimates porting timeline before submitting a formal request.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Portability check result"},"400":{"description":"Invalid phone number"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/porting/request":{"post":{"tags":["Phone Provisioning"],"summary":"Create port-in request","description":"Submits a formal number port-in request to transfer an existing phone number to the tenant's Twilio sub-account.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Port-in request created"},"400":{"description":"Invalid port request data"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/porting/requests":{"get":{"tags":["Phone Provisioning"],"summary":"List port-in requests","description":"Returns all port-in requests for the tenant with current status from Twilio.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of port-in requests"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/porting/{portInSid}":{"get":{"tags":["Phone Provisioning"],"summary":"Get port-in status","description":"Returns the current status and estimated completion date for a specific port-in request.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"portInSid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Port-in status details"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Port-in request not found"}}},"delete":{"tags":["Phone Provisioning"],"summary":"Cancel port-in request","description":"Cancels an in-progress port-in request before the scheduled porting date.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"portInSid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Port-in request cancelled"},"400":{"description":"Cannot cancel - already completed or in final state"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Port-in request not found"}}}},"/api/v3/phone-provisioning/auto":{"post":{"tags":["Phone Provisioning"],"summary":"Auto-provision number by customer address","description":"Automatically searches for and purchases a local phone number based on the customer's address area code.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Phone number provisioned"},"400":{"description":"Invalid address or no numbers available"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/available":{"get":{"tags":["Phone Provisioning"],"summary":"Search available phone numbers","description":"Searches Twilio for available local phone numbers matching the provided area code or location criteria.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of available phone numbers"},"400":{"description":"Invalid search parameters"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/purchase":{"post":{"tags":["Phone Provisioning"],"summary":"Purchase specific phone number","description":"Purchases a specific phone number from Twilio and provisions it for the tenant's messaging service.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Phone number purchased and provisioned"},"400":{"description":"Number no longer available"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/owned":{"get":{"tags":["Phone Provisioning"],"summary":"List all owned phone numbers","description":"Returns all phone numbers currently provisioned for the tenant with their capabilities and status.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of owned phone numbers"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/status":{"get":{"tags":["Phone Provisioning"],"summary":"Get auto-provisioning status for tenant","description":"Returns the tenant's overall phone provisioning configuration status and any pending actions required.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Auto-provisioning status"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/{phoneNumber}":{"get":{"tags":["Phone Provisioning"],"summary":"Get provisioning status for a number","description":"Returns the provisioning status and configuration for a specific phone number owned by the tenant.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"phoneNumber","required":true,"schema":{"type":"string"},"description":"E.164 formatted phone number"}],"responses":{"200":{"description":"Phone number provisioning status"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Phone number not found"}}},"delete":{"tags":["Phone Provisioning"],"summary":"Release phone number","description":"Releases a provisioned phone number back to Twilio, stopping billing and disabling any associated messaging.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"phoneNumber","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Phone number released"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Phone number not found"}}}},"/api/v3/phone-provisioning/sync":{"post":{"tags":["Phone Provisioning"],"summary":"Sync Twilio numbers with database","description":"Fetches all numbers from the Twilio sub-account and reconciles them with the platform database to fix any inconsistencies.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sync completed with reconciliation summary"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/phone-provisioning/trigger":{"post":{"tags":["Phone Provisioning"],"summary":"Manually trigger auto-provisioning","description":"Manually triggers the auto-provisioning workflow for tenants whose automatic provisioning failed or was deferred.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Auto-provisioning triggered"},"400":{"description":"Provisioning already complete or invalid request"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/portal/auth/request-link":{"post":{"tags":["Portal"],"summary":"Request magic link login","description":"Sends a magic link login email to the customer's address; always returns success to prevent email enumeration.","responses":{"200":{"description":"Magic link sent (always 200 to prevent enumeration)"},"400":{"description":"Invalid request body"}}}},"/api/v3/portal/auth/verify":{"post":{"tags":["Portal"],"summary":"Verify magic link token","description":"Exchanges a single-use magic link token from the email for a portal access token and user profile.","responses":{"200":{"description":"Portal access token and user profile"},"400":{"description":"Invalid request body"},"401":{"description":"Invalid or expired token"}}}},"/api/v3/portal/branding":{"get":{"tags":["Portal"],"summary":"Get tenant branding for the portal","description":"Returns the public branding configuration for the logged-in portal customer's tenant (colors, fonts, logos, company name, portal copy). Used by portal pages to inject CSS custom properties without relying on the tenant slug in the URL.","security":[{"portalAuth":[]}],"responses":{"200":{"description":"Public branding data (null when no branding is configured for the tenant)"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/invoices":{"get":{"tags":["Portal"],"summary":"Get portal user's invoices","description":"Returns all invoices for the logged-in customer's account, scoped to the current tenant portal.","security":[{"portalAuth":[]}],"responses":{"200":{"description":"List of customer invoices"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/invoices/{id}":{"get":{"tags":["Portal"],"summary":"Get invoice details","description":"Returns full details for a specific invoice belonging to the logged-in customer's account.","security":[{"portalAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Invoice details"},"401":{"description":"Portal authentication required"},"404":{"description":"Invoice not found or not owned by customer"}}}},"/api/v3/portal/me":{"get":{"tags":["Portal"],"summary":"Get current portal user","description":"Returns the authenticated portal user's profile including contact details and account associations.","security":[{"portalAuth":[]}],"responses":{"200":{"description":"Portal user profile"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/profile":{"put":{"tags":["Portal"],"summary":"Update portal user profile","description":"Allows a customer to update their contact details (name, phone, preferred contact method) through the self-service portal.","security":[{"portalAuth":[]}],"responses":{"200":{"description":"Profile updated"},"400":{"description":"Invalid profile data"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/service-requests":{"get":{"tags":["Portal"],"summary":"List portal-originated service requests","description":"Returns the authenticated customer's service requests — i.e. jobs whose `source` is `portal`. Lets the portal page surface submission history alongside the \"Submit a new request\" form on a single screen.","security":[{"portalAuth":[]}],"responses":{"200":{"description":"List of service requests (newest first)"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/submit-request":{"post":{"tags":["Portal"],"summary":"Submit a service request","description":"Allows a customer to submit a new service request through the self-service portal, automatically creating a work order for dispatch.","security":[{"portalAuth":[]}],"responses":{"201":{"description":"Service request submitted and work order created"},"400":{"description":"Invalid request body"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/work-orders":{"get":{"tags":["Portal"],"summary":"Get portal user's work orders","description":"Returns the logged-in customer's work orders so they can track service job status and history.","security":[{"portalAuth":[]}],"responses":{"200":{"description":"List of customer work orders"},"401":{"description":"Portal authentication required"}}}},"/api/v3/portal/work-orders/{id}":{"get":{"tags":["Portal"],"summary":"Get work order details","description":"Returns full details for a specific work order belonging to the logged-in customer's account, including status and scheduled dates.","security":[{"portalAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Work order details"},"401":{"description":"Portal authentication required"},"404":{"description":"Work order not found or not owned by customer"}}}},"/api/v3/predictive/analytics":{"get":{"tags":["Predictive"],"summary":"Get prediction analytics","description":"Returns aggregated failure prediction analytics for a given time period, helping operations teams understand equipment risk trends and prediction accuracy.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"periodType","schema":{"type":"string"}},{"in":"query","name":"startDate","required":true,"schema":{"type":"string","format":"date"}},{"in":"query","name":"endDate","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Prediction analytics data"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/assets":{"get":{"tags":["Predictive"],"summary":"Get asset health data with optional filtering","description":"Returns a list of assets with their predictive health scores and failure risk levels, allowing field teams to prioritize maintenance interventions.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of assets with health data"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/health":{"get":{"tags":["Predictive"],"summary":"List equipment health profiles","description":"Returns equipment health records with current health scores and maintenance history, enabling maintenance teams to monitor asset condition across the fleet.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of equipment health profiles"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}},"post":{"tags":["Predictive"],"summary":"Create or update equipment health","description":"Creates or updates a health profile for an asset, initializing or refreshing its health score based on provided condition data.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Health record created or updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/health/summary":{"get":{"tags":["Predictive"],"summary":"Get health summary","description":"Returns an aggregated summary of equipment health scores across the tenant, giving managers a high-level view of fleet condition and at-risk assets.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Equipment health summary"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/health/asset/{assetId}":{"get":{"tags":["Predictive"],"summary":"Get equipment health by asset ID","description":"Returns the health profile for a specific asset, including its current health score, failure probability, and maintenance history.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"assetId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Equipment health profile"},"401":{"description":"Authentication required"},"404":{"description":"Asset health not found"}}}},"/api/v3/predictive/health/{id}":{"get":{"tags":["Predictive"],"summary":"Get equipment health by ID","description":"Returns a specific equipment health record by its unique ID, including health scores and failure risk indicators.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Equipment health record"},"401":{"description":"Authentication required"},"404":{"description":"Health record not found"}}},"patch":{"tags":["Predictive"],"summary":"Update equipment health","description":"Updates an existing equipment health record with new condition data, adjusting the health score and risk assessment accordingly.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Health record updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Health record not found"}}}},"/api/v3/predictive/health/failure":{"post":{"tags":["Predictive"],"summary":"Record a failure event","description":"Records an actual equipment failure event, which is used to improve future failure predictions and update the asset's health profile.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Failure event recorded"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/health/recalculate":{"post":{"tags":["Predictive"],"summary":"Recalculate all health scores","description":"Triggers a recalculation of health scores for all assets in the tenant, typically run after bulk data imports or configuration changes.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Health scores recalculated"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/health/{assetId}/service":{"post":{"tags":["Predictive"],"summary":"Record a service event","description":"Records a completed service or maintenance event for an asset, which resets or improves the health score based on the work performed.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"assetId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Service event recorded"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/metrics":{"get":{"tags":["Predictive"],"summary":"Get predictive maintenance dashboard metrics","description":"Returns key performance indicators for the predictive maintenance module, including total assets monitored, high-risk counts, and prediction accuracy rates.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Predictive maintenance metrics"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/predictions":{"get":{"tags":["Predictive"],"summary":"List failure predictions","description":"Returns AI-generated failure predictions for tenant assets, ranked by risk probability so maintenance teams can proactively schedule preventive work.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of failure predictions"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/predictions/generate":{"post":{"tags":["Predictive"],"summary":"Generate failure predictions","description":"Triggers the AI model to analyze all assets and generate fresh failure predictions, useful after new health data has been imported or equipment added.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Predictions generated"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/predictions/{id}/acknowledge":{"post":{"tags":["Predictive"],"summary":"Acknowledge a prediction","description":"Marks a failure prediction as acknowledged by a team member, confirming they have reviewed the risk and are taking appropriate action.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Prediction acknowledged"},"401":{"description":"Authentication required"},"404":{"description":"Prediction not found"}}}},"/api/v3/predictive/predictions/{id}/preventive-action":{"post":{"tags":["Predictive"],"summary":"Record preventive action for prediction","description":"Records that a preventive maintenance action has been taken in response to a prediction, closing the feedback loop for model improvement.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Preventive action recorded"},"401":{"description":"Authentication required"},"404":{"description":"Prediction not found"}}}},"/api/v3/predictive/predictions/{id}/actual-failure":{"post":{"tags":["Predictive"],"summary":"Record actual failure for prediction","description":"Records that a predicted failure actually occurred, providing ground-truth data to the AI model for ongoing accuracy improvement.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Actual failure recorded"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Prediction not found"}}}},"/api/v3/predictive/schedules":{"get":{"tags":["Predictive"],"summary":"List maintenance schedules","description":"Returns AI-recommended maintenance schedules for assets based on failure risk scores, helping teams optimize when and how frequently to service equipment.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of maintenance schedules"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}},"post":{"tags":["Predictive"],"summary":"Create maintenance schedule","description":"Creates a new predictive maintenance schedule for an asset, defining the service frequency and conditions that trigger maintenance recommendations.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Maintenance schedule created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/predictive/schedules/{id}":{"patch":{"tags":["Predictive"],"summary":"Update maintenance schedule","description":"Updates an existing predictive maintenance schedule, adjusting service intervals or conditions based on updated risk assessments.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Maintenance schedule updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Schedule not found"}}}},"/api/v3/predictive/settings":{"get":{"tags":["Predictive"],"summary":"Get predictive maintenance settings","description":"Returns the tenant's predictive maintenance configuration, including AI model thresholds, custom failure rules, and alert preferences.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Predictive maintenance settings"},"401":{"description":"Authentication required"}}},"patch":{"tags":["Predictive"],"summary":"Update predictive maintenance settings","description":"Updates the tenant's predictive maintenance configuration, allowing admins to tune risk thresholds and define custom failure detection rules.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Settings updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/approvals":{"get":{"tags":["PriceBook"],"summary":"List price approval requests","description":"Returns price override approval requests submitted by field techs, filterable by status so managers can review and act on pending discount requests.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"status","schema":{"type":"string","enum":["pending","approved","rejected"]}}],"responses":{"200":{"description":"List of approval requests"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create price approval request","description":"Submits a request to override a pricebook item's price, requiring manager approval before the discounted price can be applied to a job.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Approval request created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/approvals/{requestId}/decide":{"post":{"tags":["PriceBook"],"summary":"Approve or reject price approval request","description":"Allows a manager to approve or reject a pending price override request, controlling whether discounted pricing can be applied to the associated job.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"requestId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Decision recorded"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Approval request not found"}}}},"/api/v3/pricebook/volume-discounts":{"get":{"tags":["PriceBook"],"summary":"List volume discounts","description":"Returns volume discount rules that automatically reduce pricing when customers purchase above specified quantity thresholds.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"priceBookId","schema":{"type":"string"}}],"responses":{"200":{"description":"List of volume discounts"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create volume discount","description":"Creates a new volume discount rule that automatically applies percentage or per-unit discounts when order quantities exceed the minimum threshold.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Volume discount created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/volume-discounts/{discountId}":{"delete":{"tags":["PriceBook"],"summary":"Delete volume discount","description":"Permanently removes a volume discount rule so it no longer applies to future quotes and invoices.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"discountId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Volume discount deleted"},"401":{"description":"Authentication required"},"404":{"description":"Volume discount not found"}}}},"/api/v3/pricebook/{id}/history":{"get":{"tags":["PriceBook"],"summary":"Get audit history for item","description":"Returns the change history for a pricebook item, showing all price modifications, who made them, and when — supporting pricing accountability and dispute resolution.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Audit history for pricebook item"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook/{id}/restore":{"post":{"tags":["PriceBook"],"summary":"Restore archived item","description":"Unarchives a previously deleted pricebook item, making it active and available for use in new estimates and work orders.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Item restored"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook/bundles":{"get":{"tags":["PriceBook"],"summary":"List bundles","description":"Returns all pricebook bundles (grouped services/parts) that can be added as a single line item to estimates and work orders.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of bundles"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create bundle","description":"Creates a new pricebook bundle that groups multiple services or parts into a single sellable package with a combined price.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Bundle created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/{id}/bundles":{"get":{"tags":["PriceBook"],"summary":"Get bundles for a specific item","description":"Returns all bundles that include a specific pricebook item, helping field technicians identify package deals when adding services to a job.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bundles containing the specified item"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook/bundles/{bundleId}":{"patch":{"tags":["PriceBook"],"summary":"Update bundle","description":"Updates a pricebook bundle's name, description, or pricing details.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Bundle updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Bundle not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete bundle","description":"Permanently removes a pricebook bundle, preventing it from being added to future estimates and work orders.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Bundle deleted"},"401":{"description":"Authentication required"},"404":{"description":"Bundle not found"}}}},"/api/v3/pricebook/bundles/{bundleId}/items":{"get":{"tags":["PriceBook"],"summary":"Get bundle items","description":"Returns all pricebook items included in a specific bundle, with their individual quantities and pricing.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Bundle items"},"401":{"description":"Authentication required"},"404":{"description":"Bundle not found"}}},"post":{"tags":["PriceBook"],"summary":"Add item to bundle","description":"Adds an existing pricebook item to a bundle with a specified quantity, expanding the package offering.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Item added to bundle"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Bundle or item not found"}}}},"/api/v3/pricebook/bundles/{bundleId}/items/{itemId}":{"delete":{"tags":["PriceBook"],"summary":"Remove item from bundle","description":"Removes a specific pricebook item from a bundle without deleting the item itself or the bundle.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Item removed from bundle"},"401":{"description":"Authentication required"},"404":{"description":"Bundle or item not found"}}}},"/api/v3/pricebook/bundles/{bundleId}/price":{"get":{"tags":["PriceBook"],"summary":"Calculate bundle price","description":"Calculates the total price for a bundle by summing the current prices of all included items, reflecting any active pricing rules or tier overrides.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Calculated bundle price"},"401":{"description":"Authentication required"},"404":{"description":"Bundle not found"}}}},"/api/v3/pricebook/export.csv":{"get":{"tags":["PriceBook"],"summary":"Export pricebook items to CSV","description":"Exports all pricebook items for the tenant as a downloadable CSV file, useful for bulk price reviews, spreadsheet analysis, or backup purposes.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"CSV file of pricebook items","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/template.csv":{"get":{"tags":["PriceBook"],"summary":"Download empty CSV import template","description":"Downloads a blank CSV template with the correct column headers for bulk-importing pricebook items, allowing customers to prepare their data offline.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Empty CSV import template","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/gbb/templates":{"get":{"tags":["PriceBook"],"summary":"List Good-Better-Best templates","description":"Returns all Good-Better-Best proposal templates, which present customers with tiered service options (Good, Better, Best) to increase average ticket value.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of GBB templates"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create Good-Better-Best template","description":"Creates a new GBB proposal template that can be attached to estimates, presenting the customer with multiple service tiers to choose from.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"GBB template created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/gbb/templates/{templateId}":{"get":{"tags":["PriceBook"],"summary":"Get a single Good-Better-Best template with its options","description":"Returns one GBB template plus its tiered options. Frontend calls this every time a row is expanded; without it the inline editor cannot show its three columns and \"Add\" buttons appear to no-op.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"templateId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"GBB template with options"},"401":{"description":"Authentication required"},"404":{"description":"Template not found"}}},"patch":{"tags":["PriceBook"],"summary":"Update Good-Better-Best template","description":"Updates a GBB template's name, description, or industry classification.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"templateId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"GBB template updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Template not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete Good-Better-Best template","description":"Permanently removes a GBB template and all its tier options, preventing it from being used in future proposals.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"templateId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"GBB template deleted"},"401":{"description":"Authentication required"},"404":{"description":"Template not found"}}}},"/api/v3/pricebook/gbb/templates/{templateId}/options":{"get":{"tags":["PriceBook"],"summary":"List GBB template options","description":"Returns all tier options (Good, Better, Best) for a GBB template, including the pricebook items, features, and pricing for each tier.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"templateId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of GBB tier options"},"401":{"description":"Authentication required"},"404":{"description":"Template not found"}}},"post":{"tags":["PriceBook"],"summary":"Add option to GBB template","description":"Adds a tier option (Good, Better, or Best) to a GBB template, linking it to a pricebook item and defining the features presented to the customer.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"templateId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"GBB option added"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Template not found"}}}},"/api/v3/pricebook/gbb/options/{optionId}":{"patch":{"tags":["PriceBook"],"summary":"Update GBB template option","description":"Updates a tier option's display name, linked pricebook item, description, features, or recommended flag.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"optionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"GBB option updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Option not found"}}},"delete":{"tags":["PriceBook"],"summary":"Remove GBB template option","description":"Removes a tier option from a GBB template, reducing the number of choices presented to customers on future proposals.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"optionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"GBB option removed"},"401":{"description":"Authentication required"},"404":{"description":"Option not found"}}}},"/api/v3/pricebook/gbb/options/{optionId}/lines":{"get":{"tags":["PriceBook"],"summary":"List the lines of a GBB option","description":"Returns the pricebook-item references (item + quantity) that make up a Good-Better-Best tier option, from which its price is computed.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"optionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of option lines"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Add a line to a GBB option","description":"References a pricebook item (with a quantity and optional per-line price override) inside a Good-Better-Best tier option.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"optionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Option line added"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Option not found"}}}},"/api/v3/pricebook/gbb/options/{optionId}/lines/{lineId}":{"patch":{"tags":["PriceBook"],"summary":"Update a GBB option line","description":"Updates a line's quantity, per-line price override, sort order, or the linked pricebook item.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"optionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"lineId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Option line updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Option line not found"}}},"delete":{"tags":["PriceBook"],"summary":"Remove a GBB option line","description":"Removes a pricebook-item reference from a Good-Better-Best tier option.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"optionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"lineId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Option line removed"},"401":{"description":"Authentication required"},"404":{"description":"Option line not found"}}}},"/api/v3/pricebook/import":{"post":{"tags":["PriceBook"],"summary":"Import pricebook items from CSV","description":"Bulk-imports pricebook items from a CSV file, allowing admins to quickly populate or update large catalogs without manual data entry. Requires admin, manager, or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Import results with success and error counts"},"400":{"description":"Invalid CSV format or data"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/services/{id}":{"get":{"tags":["PriceBook"],"summary":"Get service by ID","description":"Returns a specific pricebook service item by its ID, including pricing, description, and category.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Service item details"},"401":{"description":"Authentication required"},"404":{"description":"Service not found"}}}},"/api/v3/pricebook/{id}":{"get":{"tags":["PriceBook"],"summary":"Get pricebook item by ID","description":"Returns a specific pricebook item by its ID, including pricing, category, and status information.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pricebook item details"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}},"patch":{"tags":["PriceBook"],"summary":"Update pricebook item","description":"Updates a pricebook item's details such as name, price, category, or description; changes are tracked in the audit history.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pricebook item updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete pricebook item","description":"Archives (soft-deletes) a pricebook item so it is no longer available for new jobs, while preserving historical data on existing estimates and invoices.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pricebook item deleted"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook/{id}/full":{"get":{"tags":["PriceBook"],"summary":"Get pricebook item with full details","description":"Returns a pricebook item with its complete related data including materials, labor rates, tier prices, and audit history.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pricebook item with full details"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook":{"post":{"tags":["PriceBook"],"summary":"Create pricebook item","description":"Creates a new pricebook item (service, part, or labor) that can be added to estimates, work orders, and bundles.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Pricebook item created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}},"get":{"tags":["PriceBook"],"summary":"List pricebook items","description":"Returns paginated pricebook items filterable by category, industry, and status, used by field technicians to look up services and parts during estimating.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"includeArchived","schema":{"type":"string","enum":[true,false]},"description":"Include archived items in the page."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":2000,"default":500},"description":"Page size. Defaults to the historical 500-row cap."},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Rows skipped before the page starts."}],"responses":{"200":{"description":"List of pricebook items"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/labor-rate-presets":{"get":{"tags":["PriceBook"],"summary":"List reusable labor-rate presets for the tenant","description":"Returns the distinct labor rates already defined across the tenant's price book (one per name), so the service editor can offer quick-pick instead of retyping a known rate.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of labor-rate presets"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/{id}/labor-rates":{"get":{"tags":["PriceBook"],"summary":"List labor rates for pricebook item","description":"Returns all labor rates associated with a pricebook service item, including standard, overtime, and minimum hour rates used in job cost calculations.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of labor rates"},"401":{"description":"Authentication required"},"404":{"description":"Pricebook item not found"}}},"post":{"tags":["PriceBook"],"summary":"Create labor rate for pricebook item","description":"Adds a new labor rate to a pricebook service item, defining the hourly rate, minimum hours, and overtime multiplier to be used when generating job invoices.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Labor rate created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Pricebook item not found"}}}},"/api/v3/pricebook/labor-rates/{id}":{"patch":{"tags":["PriceBook"],"summary":"Update labor rate","description":"Updates a labor rate's hourly rate, minimum hours, or overtime multiplier to reflect current labor costs.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Labor rate updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Labor rate not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete labor rate","description":"Removes a labor rate from a pricebook item so it is no longer applied in future job cost calculations.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Labor rate deleted"},"401":{"description":"Authentication required"},"404":{"description":"Labor rate not found"}}}},"/api/v3/pricebook/industries":{"get":{"tags":["PriceBook"],"summary":"List available industries","description":"Returns the list of industry categories available in the pricebook, used to filter and organize services by vertical market.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of available industries"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/categories":{"get":{"tags":["PriceBook"],"summary":"List pricebook categories","description":"Returns all pricebook categories, optionally filtered by industry, enabling organized navigation of services and parts during job estimating.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of pricebook categories"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/summary":{"get":{"tags":["PriceBook"],"summary":"Get pricebook summary","description":"Returns summary statistics for the pricebook including total item counts, category breakdown, and recent updates.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Pricebook summary statistics"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/search":{"get":{"tags":["PriceBook"],"summary":"Search pricebook items","description":"Full-text search across pricebook items by name, description, or part number, enabling quick lookup of services and parts in the field.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Matching pricebook items"},"400":{"description":"Invalid search query"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/services":{"get":{"tags":["PriceBook"],"summary":"List pricebook services","description":"Returns only service-type pricebook items (excluding parts and materials), used when populating service line items on work orders and estimates.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of pricebook services"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/{id}/materials":{"get":{"tags":["PriceBook"],"summary":"List materials for pricebook item","description":"Returns all materials (parts and supplies) associated with a pricebook service item, used to calculate accurate job costs during estimating.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of materials"},"401":{"description":"Authentication required"},"404":{"description":"Pricebook item not found"}}},"post":{"tags":["PriceBook"],"summary":"Create material for pricebook item","description":"Adds a new material (part, supply, or component) to a pricebook service item so it is included in cost calculations for future estimates.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Material created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Pricebook item not found"}}}},"/api/v3/pricebook/materials/{id}":{"patch":{"tags":["PriceBook"],"summary":"Update material","description":"Updates a material's details such as unit cost, quantity, supplier, or part number to keep cost data current.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Material updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Material not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete material","description":"Removes a material from a pricebook item so it is no longer included in job cost calculations.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Material deleted"},"401":{"description":"Authentication required"},"404":{"description":"Material not found"}}}},"/api/v3/pricebook/price-lists":{"get":{"tags":["PriceBook"],"summary":"List price lists","description":"Returns all seasonal or promotional price lists that override standard pricebook pricing during their active date range.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of price lists"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create price list","description":"Creates a new seasonal or promotional price list with a defined effective date range during which its item prices override the standard pricebook rates.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Price list created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/price-lists/{listId}":{"patch":{"tags":["PriceBook"],"summary":"Update price list","description":"Updates a price list's name, effective dates, or status to extend, shorten, or rename a promotional pricing period.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"listId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Price list updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Price list not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete price list","description":"Permanently removes a price list and all its item overrides, ending the promotional pricing immediately.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"listId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Price list deleted"},"401":{"description":"Authentication required"},"404":{"description":"Price list not found"}}}},"/api/v3/pricebook/price-lists/{listId}/items":{"get":{"tags":["PriceBook"],"summary":"Get price list items","description":"Returns all pricebook items included in a price list with their overridden promotional prices.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"listId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Price list items with override prices"},"401":{"description":"Authentication required"},"404":{"description":"Price list not found"}}},"post":{"tags":["PriceBook"],"summary":"Set item in price list","description":"Adds or updates a pricebook item's promotional price within a price list, overriding the standard rate during the list's active period.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"listId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Item price set in price list"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Price list not found"}}}},"/api/v3/pricebook/price-lists/{listId}/items/{itemId}":{"delete":{"tags":["PriceBook"],"summary":"Remove item from price list","description":"Removes a pricebook item from a price list, restoring it to standard pricing for the duration of the promotional period.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"listId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"itemId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Item removed from price list"},"401":{"description":"Authentication required"},"404":{"description":"Item not found in price list"}}}},"/api/v3/pricebook/effective-price/{itemId}":{"get":{"tags":["PriceBook"],"summary":"Get effective price for item","description":"Resolves and returns the currently effective price for a pricebook item, taking into account any active price lists, tier overrides, or promotional pricing.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"itemId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Effective price for item"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook/pricing-rules":{"post":{"tags":["PriceBook"],"summary":"Create pricing rule","description":"Creates a new automated pricing rule (markup ladder, margin target, or cost-plus) that automatically adjusts item prices based on defined conditions and actions.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Pricing rule created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}},"get":{"tags":["PriceBook"],"summary":"List pricing rules","description":"Returns all automated pricing rules (markup ladders, margin targets, cost-plus rules) that are applied when calculating item prices for estimates and invoices.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of pricing rules"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/pricing-rules/{ruleId}":{"patch":{"tags":["PriceBook"],"summary":"Update pricing rule","description":"Updates a pricing rule's conditions, actions, or active status to refine how automatic price calculations are applied.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"ruleId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Pricing rule updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Pricing rule not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete pricing rule","description":"Permanently removes a pricing rule so it no longer affects automatic price calculations for estimates and invoices.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"ruleId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Pricing rule deleted"},"401":{"description":"Authentication required"},"404":{"description":"Pricing rule not found"}}}},"/api/v3/pricebook/tiers":{"get":{"tags":["PriceBook"],"summary":"List pricing tiers","description":"Returns all customer pricing tiers (e.g., Gold, Silver, Bronze) that apply automatic discounts or custom prices to accounts assigned to that tier.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of pricing tiers"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create pricing tier","description":"Creates a new customer pricing tier with a defined discount type and value that is automatically applied to jobs for accounts in that tier.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Pricing tier created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/tiers/{tierId}":{"patch":{"tags":["PriceBook"],"summary":"Update pricing tier","description":"Updates an existing pricing tier's name, discount value, or priority order.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"tierId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Pricing tier updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Tier not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete pricing tier","description":"Removes a pricing tier, which will stop its discount from being applied to accounts previously assigned to it.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"tierId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Pricing tier deleted"},"401":{"description":"Authentication required"},"404":{"description":"Tier not found"}}}},"/api/v3/pricebook/tiers/{tierId}/prices":{"get":{"tags":["PriceBook"],"summary":"Get tier prices","description":"Returns all custom price overrides configured for a pricing tier, showing which items have tier-specific pricing instead of the standard discount.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"tierId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of tier price overrides"},"401":{"description":"Authentication required"},"404":{"description":"Tier not found"}}},"post":{"tags":["PriceBook"],"summary":"Set tier price","description":"Sets a custom price override for a specific pricebook item within a tier, overriding the percentage discount with a fixed price for tier customers.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"tierId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Tier price set"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Tier not found"}}}},"/api/v3/pricebook/tiers/{tierId}/prices/{priceId}":{"delete":{"tags":["PriceBook"],"summary":"Remove tier price","description":"Removes a custom price override from a tier, reverting the item back to the tier's standard percentage discount.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"tierId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"priceId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Tier price removed"},"401":{"description":"Authentication required"},"404":{"description":"Tier price not found"}}}},"/api/v3/pricebook/resolve-price/{itemId}":{"get":{"tags":["PriceBook"],"summary":"Resolve effective price for item","description":"Calculates and returns the effective price for a pricebook item after applying any active tier discounts, volume pricing, or custom price overrides for a given account.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"itemId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Effective price for item"},"401":{"description":"Authentication required"},"404":{"description":"Item not found"}}}},"/api/v3/pricebook/vendors":{"get":{"tags":["PriceBook"],"summary":"List vendors","description":"Returns all supplier/vendor records associated with pricebook materials, enabling procurement tracking and supplier management.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of vendors"},"401":{"description":"Authentication required"}}},"post":{"tags":["PriceBook"],"summary":"Create vendor","description":"Creates a new supplier/vendor record with contact details and account information, making them available for association with pricebook materials.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Vendor created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/pricebook/vendors/{vendorId}":{"patch":{"tags":["PriceBook"],"summary":"Update vendor","description":"Updates a vendor's contact information, payment terms, or account number.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Vendor updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Vendor not found"}}},"delete":{"tags":["PriceBook"],"summary":"Delete vendor","description":"Removes a vendor record from the system; this does not affect existing material associations.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Vendor deleted"},"401":{"description":"Authentication required"},"404":{"description":"Vendor not found"}}}},"/api/v3/quickbooks/sync/all":{"post":{"tags":["QuickBooks"],"summary":"Run full QuickBooks sync","description":"Triggers a full bidirectional sync between the platform and QuickBooks Online, reconciling customers, invoices, and payments. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sync completed with results"},"400":{"description":"Invalid sync options"},"401":{"description":"Authentication required"}}}},"/api/v3/quickbooks/sync/history":{"get":{"tags":["QuickBooks"],"summary":"Get QuickBooks sync history","description":"Returns the log of recent QuickBooks sync operations including start time, status, record counts, and any errors encountered during each sync.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"description":"List of sync history records"},"401":{"description":"Authentication required"}}}},"/v3/reports":{"get":{"tags":["Reports"],"summary":"Catalog of available reports","description":"Lists every report type the platform exposes plus its status\n(`available` if the per-report endpoint is implemented, `planned`\nif it's still in the slice plan for.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Reports catalog"},"401":{"description":"Authentication required"}}}},"/v3/reports/preview":{"post":{"tags":["Reports"],"summary":"Generate preview data for report builder","description":"Executes a report query with the provided configuration and returns a preview of the data, allowing users to verify results before saving the report.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Report preview data"},"400":{"description":"Invalid report configuration"},"401":{"description":"Authentication required"}}}},"/v3/reports/saved":{"get":{"tags":["Reports"],"summary":"Get all saved reports for the tenant","description":"Returns all report definitions saved by users in the tenant, enabling reuse of frequently run reports from the report library.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of saved reports"},"401":{"description":"Authentication required"}}}},"/v3/reports/save":{"post":{"tags":["Reports"],"summary":"Save a new report","description":"Saves a report configuration with its filters and column settings so it can be rerun from the report library without reconfiguration.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Report saved"},"400":{"description":"Invalid report configuration"},"401":{"description":"Authentication required"}}}},"/v3/reports/{id}":{"get":{"tags":["Reports"],"summary":"Get a specific saved report with its configuration","description":"Returns a saved report's configuration and metadata, used to pre-populate the report builder when a user wants to view or modify an existing saved report.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Saved report with configuration"},"401":{"description":"Authentication required"},"404":{"description":"Report not found"}}},"delete":{"tags":["Reports"],"summary":"Delete a saved report","description":"Permanently removes a saved report from the report library; running the report again would require manual reconfiguration.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Report deleted"},"401":{"description":"Authentication required"},"404":{"description":"Report not found"}}}},"/api/v3/roles":{"get":{"tags":["Roles"],"summary":"List all roles","description":"Returns all custom roles defined for the tenant, along with their permission sets, used to manage staff access control.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of roles"},"401":{"description":"Authentication required"}}},"post":{"tags":["Roles"],"summary":"Create a new role","description":"Creates a custom role with a defined permission set, enabling admins to tailor access control for different staff types within the organization.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Role created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/roles/{id}":{"get":{"tags":["Roles"],"summary":"Get role by ID","description":"Returns a specific role and its full permission set, used to review or edit access control configuration for a role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Role details with permissions"},"401":{"description":"Authentication required"},"404":{"description":"Role not found"}}},"put":{"tags":["Roles"],"summary":"Update a role","description":"Updates a role's name or permission set; changes take effect immediately for all users assigned that role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Role updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Role not found"}}},"delete":{"tags":["Roles"],"summary":"Delete a role","description":"Permanently removes a custom role from the tenant; users previously assigned this role will lose any permissions it granted.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Role deleted"},"401":{"description":"Authentication required"},"404":{"description":"Role not found"}}}},"/api/v3/routing/settings":{"get":{"tags":["Routing"],"summary":"Get route optimization settings","description":"Returns the tenant's AI route optimization settings including preferred routing mode, constraints, and optimization objectives.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Route optimization settings"},"401":{"description":"Authentication required"}}},"patch":{"tags":["Routing"],"summary":"Update route optimization settings","description":"Updates the route optimization configuration such as max drive time, preferred routing algorithm, and working hour constraints. Requires manager or above.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Settings updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/optimize":{"post":{"tags":["Routing"],"summary":"Run route optimization","description":"Runs AI-powered route optimization for a set of jobs and technicians, returning an optimized schedule that minimizes total drive time and respects skill/location constraints.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Optimized route plan"},"400":{"description":"Invalid optimization request"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/optimizations/{id}/accept":{"post":{"tags":["Routing"],"summary":"Accept a route optimization","description":"Accepts an AI-generated route optimization, applying the recommended schedule and dispatching assignments to the relevant technicians.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Optimization accepted and applied"},"401":{"description":"Authentication required"},"404":{"description":"Optimization not found"}}}},"/api/v3/routing/optimizations/{id}/reject":{"post":{"tags":["Routing"],"summary":"Reject a route optimization","description":"Rejects an AI-generated route optimization with an optional reason, discarding the suggested schedule and keeping existing assignments unchanged.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Optimization rejected"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Optimization not found"}}}},"/api/v3/routing/history":{"get":{"tags":["Routing"],"summary":"Get route optimization history","description":"Returns the history of past route optimization runs with their results, allowing managers to review accepted and rejected optimizations over time.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of optimization history records"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/analytics":{"get":{"tags":["Routing"],"summary":"Get routing analytics","description":"Returns analytics on route optimization performance, including average drive time savings, acceptance rates, and fuel efficiency improvements over a time period.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Routing analytics data"},"400":{"description":"Invalid query parameters"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/routes":{"get":{"tags":["Routing"],"summary":"List optimized routes (stub — empty until aggregation lands)"}},"/api/v3/routing/metrics":{"get":{"tags":["Routing"],"summary":"Route optimization metrics (stub — zeros until aggregation lands)"}},"/api/v3/routing/traffic":{"post":{"tags":["Routing"],"summary":"Get traffic data for a route","description":"Retrieves real-time or predicted traffic conditions for a given route, used to update estimated arrival times and adjust technician schedules proactively.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Traffic data for route"},"400":{"description":"Invalid route specification"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/traffic/check-updates":{"post":{"tags":["Routing"],"summary":"Check for traffic updates","description":"Checks whether traffic conditions have changed significantly for active routes, triggering re-routing recommendations when delays are detected.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Traffic update status"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/traffic/patterns":{"get":{"tags":["Routing"],"summary":"Get traffic patterns","description":"Returns historical traffic patterns for a service area, enabling predictive scheduling that avoids known congestion windows.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Traffic pattern data"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/events":{"post":{"tags":["Routing"],"summary":"Log a routing event","description":"Records a routing-related event (e.g., technician departure, arrival, route deviation) for analytics and real-time dispatch monitoring.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Event logged"},"400":{"description":"Invalid event data"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/geocode":{"post":{"tags":["Routing"],"summary":"Geocode an address","description":"Converts a street address into GPS coordinates, used to plot job locations on the map and include them in route optimization calculations.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Geocoded coordinates"},"400":{"description":"Invalid address"},"401":{"description":"Authentication required"}}}},"/api/v3/routing/geocode/batch":{"post":{"tags":["Routing"],"summary":"Batch geocode addresses","description":"Converts multiple street addresses to GPS coordinates in a single request, optimizing geocoding efficiency when processing large job imports or data migrations.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Batch geocoded coordinates"},"400":{"description":"Invalid addresses"},"401":{"description":"Authentication required"}}}},"/api/v3/safety-standards":{"post":{"tags":["Safety Standards"],"summary":"Create safety standard","description":"Creates a new safety standard entry (e.g., OSHA, EPA, local code) applicable to specific job types, ensuring field technicians are informed of required safety protocols.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Safety standard created"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"}}},"get":{"tags":["Safety Standards"],"summary":"Get all safety standards","description":"Returns all active safety standards for the tenant, optionally including inactive ones, used to display applicable regulations on job safety checklists.","security":[{"bearerAuth":[]}],"parameters":[{"name":"includeInactive","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"List of safety standards"},"401":{"description":"Authentication required"}}}},"/api/v3/safety-standards/{code}":{"put":{"tags":["Safety Standards"],"summary":"Update safety standard","description":"Updates an existing safety standard's requirements, effective date, or applicable job types by its regulatory code.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Safety standard updated"},"400":{"description":"Invalid request body"},"401":{"description":"Authentication required"},"404":{"description":"Safety standard not found"}}},"get":{"tags":["Safety Standards"],"summary":"Get safety standard by code","description":"Returns a specific safety standard by its regulatory code, including requirements, applicable job types, and effective dates.","security":[{"bearerAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Safety standard details"},"401":{"description":"Authentication required"},"404":{"description":"Standard not found"}}}},"/api/v3/safety-standards/{code}/permanent":{"delete":{"tags":["Safety Standards"],"summary":"Deactivate safety standard","description":"Deactivates a safety standard so it no longer appears in job safety checklists; the standard is retained for historical compliance records.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Safety standard deactivated"},"401":{"description":"Authentication required"},"404":{"description":"Safety standard not found"}}}},"/api/v3/safety-standards/osha-info":{"get":{"tags":["Safety Standards"],"summary":"Get OSHA revision info","description":"Returns current OSHA regulation revision metadata, allowing the platform to identify when standards have been updated and prompt admins to review their configurations.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OSHA revision information"},"401":{"description":"Authentication required"}}}},"/api/v3/safety-standards/analyze":{"post":{"tags":["Safety Standards"],"summary":"Analyze job safety requirements","description":"Analyzes a job's type, location, and equipment to identify applicable safety standards and generate a job-specific safety checklist for field technicians.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Applicable safety requirements for job"},"400":{"description":"Invalid job specification"},"401":{"description":"Authentication required"}}}},"/api/v3/schedule/breaks/date/{date}":{"get":{"tags":["Schedule"],"summary":"Get all breaks for a date","description":"Retrieve all scheduled technician breaks for a specific calendar date.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"date","required":true,"schema":{"type":"string","format":"date"},"description":"Calendar date (YYYY-MM-DD)"}],"responses":{"200":{"description":"List of breaks for the date"},"400":{"description":"Invalid date format"},"401":{"description":"Unauthorized"}}}},"/api/v3/schedule/breaks/technician/{userId}":{"get":{"tags":["Schedule"],"summary":"Get breaks for a technician","description":"Retrieve all breaks for a specific technician within a date range.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start of date range (YYYY-MM-DD)"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End of date range (YYYY-MM-DD)"}],"responses":{"200":{"description":"List of technician breaks"},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"},"404":{"description":"Technician not found"}}}},"/api/v3/schedule/breaks":{"post":{"tags":["Schedule"],"summary":"Create a break","description":"Schedule a break (vacation, sick day, or other) for a technician. Requires manager or admin role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBreak"}}}},"responses":{"201":{"description":"Break created successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"}}}},"/api/v3/schedule/breaks/{id}":{"delete":{"tags":["Schedule"],"summary":"Delete a break","description":"Remove a scheduled break by ID. Requires manager or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Break record ID"}],"responses":{"200":{"description":"Break deleted successfully"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"},"404":{"description":"Break not found"}}}},"/api/v3/schedule/optimization-logs":{"get":{"tags":["Schedule"],"summary":"Get recent optimization logs","description":"Retrieve recent schedule optimization run logs including outcomes and metrics. Requires manager or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Number of log entries to return"}],"responses":{"200":{"description":"List of optimization log entries"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"}}}},"/api/v3/schedule/preferences":{"get":{"tags":["Schedule"],"summary":"Get all scheduling preferences","description":"Retrieve tenant-level scheduling configuration preferences such as buffer times, optimization settings, and default rules.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Scheduling preferences object"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Schedule"],"summary":"Set a scheduling preference","description":"Create or update a scheduling preference key-value pair. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPreference"}}}},"responses":{"200":{"description":"Preference saved successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/schedule/service-durations":{"get":{"tags":["Schedule"],"summary":"Get all service duration estimates","description":"Retrieve configured time estimates for each service type, used for scheduling slot allocation.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of service duration estimates"},"401":{"description":"Unauthorized"}}}},"/api/v3/schedule/service-durations/{id}":{"put":{"tags":["Schedule"],"summary":"Update service duration estimate","description":"Update the estimated duration for a specific service type. Requires manager or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Service duration record ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateServiceDuration"}}}},"responses":{"200":{"description":"Service duration updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"},"404":{"description":"Service duration record not found"}}}},"/api/v3/schedule/slots/date/{date}":{"get":{"tags":["Schedule"],"summary":"Get all slots for a date","description":"Retrieve all schedule slots across all technicians for a specific calendar date.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"date","required":true,"schema":{"type":"string","format":"date"},"description":"Calendar date (YYYY-MM-DD)"}],"responses":{"200":{"description":"List of schedule slots"},"400":{"description":"Invalid date format"},"401":{"description":"Unauthorized"}}}},"/api/v3/schedule/slots/technician/{userId}":{"get":{"tags":["Schedule"],"summary":"Get slots for a technician","description":"Retrieve all schedule slots for a specific technician within an optional date range.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start of date range"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End of date range"}],"responses":{"200":{"description":"List of technician's schedule slots"},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"},"404":{"description":"Technician not found"}}}},"/api/v3/schedule/slots/{id}":{"get":{"tags":["Schedule"],"summary":"Get slot by ID","description":"Retrieve a specific schedule slot by its unique identifier.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Schedule slot ID"}],"responses":{"200":{"description":"Schedule slot details"},"401":{"description":"Unauthorized"},"404":{"description":"Slot not found"}}},"put":{"tags":["Schedule"],"summary":"Update a schedule slot","description":"Update the time, technician, or job assignment of an existing schedule slot.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Schedule slot ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSlot"}}}},"responses":{"200":{"description":"Schedule slot updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role"},"404":{"description":"Slot not found"}}},"delete":{"tags":["Schedule"],"summary":"Delete a schedule slot","description":"Remove a schedule slot by ID. Requires dispatcher, manager, or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Schedule slot ID"}],"responses":{"200":{"description":"Schedule slot deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role"},"404":{"description":"Slot not found"}}}},"/api/v3/schedule/slots":{"post":{"tags":["Schedule"],"summary":"Create a schedule slot","description":"Create a new schedule slot for a technician. Requires dispatcher, manager, or admin role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSlot"}}}},"responses":{"201":{"description":"Schedule slot created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role"}}}},"/api/v3/schedule/supply-stores":{"get":{"tags":["Schedule"],"summary":"Get all supply stores","description":"Retrieve all supply store locations used for technician resupply routing and scheduling.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of supply stores"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Schedule"],"summary":"Create a supply store","description":"Add a new supply store location to the scheduling system. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplyStore"}}}},"responses":{"201":{"description":"Supply store created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/schedule/supply-stores/{id}":{"get":{"tags":["Schedule"],"summary":"Get supply store by ID","description":"Retrieve details of a specific supply store location by its ID.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Supply store ID"}],"responses":{"200":{"description":"Supply store details"},"401":{"description":"Unauthorized"},"404":{"description":"Supply store not found"}}},"delete":{"tags":["Schedule"],"summary":"Delete a supply store","description":"Remove a supply store location from the system. Requires admin or owner role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Supply store ID"}],"responses":{"200":{"description":"Supply store deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"},"404":{"description":"Supply store not found"}}}},"/api/v3/schedule/technicians/{userId}/week":{"get":{"tags":["Schedule"],"summary":"Get weekly schedule pattern for a technician","description":"Retrieve the recurring weekly schedule pattern (working days and hours) for a specific technician.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"}],"responses":{"200":{"description":"Weekly schedule pattern"},"401":{"description":"Unauthorized"},"404":{"description":"Technician not found"}}}},"/api/v3/schedule/technicians/{userId}/day/{date}":{"get":{"tags":["Schedule"],"summary":"Get full day schedule for a technician","description":"Retrieve the complete daily schedule including all slots, breaks, and jobs for a technician on a specific date.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"},{"in":"path","name":"date","required":true,"schema":{"type":"string","format":"date"},"description":"Calendar date (YYYY-MM-DD)"}],"responses":{"200":{"description":"Full day schedule details"},"400":{"description":"Invalid date format"},"401":{"description":"Unauthorized"},"404":{"description":"Technician not found"}}}},"/api/v3/schedule/technicians/pattern":{"post":{"tags":["Schedule"],"summary":"Set weekly schedule pattern","description":"Define or update the recurring weekly availability pattern for a technician. Requires manager or admin role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TechnicianSchedule"}}}},"responses":{"200":{"description":"Weekly pattern saved"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"}}}},"/api/v3/schedule/technicians/{userId}/init":{"post":{"tags":["Schedule"],"summary":"Initialize default schedule for technician","description":"Bootstrap a new technician's schedule with default working hours pattern. Requires manager or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"}],"responses":{"200":{"description":"Default schedule initialized"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"},"404":{"description":"Technician not found"}}}},"/api/v3/schedule/totals":{"get":{"tags":["Schedule"],"summary":"Schedule revenue + count totals (cancelled excluded)","description":"Returns active vs cancelled job counts and revenue for the requested\nwindow. Active aggregates exclude `status = 'cancelled'` so day/week\ntotals on the schedule board match the Revenue Breakdown tile.","parameters":[{"in":"query","name":"startDate","required":true,"schema":{"type":"string","format":"date"}},{"in":"query","name":"endDate","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Totals payload"},"400":{"description":"Missing or malformed date params"},"401":{"description":"Unauthorized"}}}},"/api/v3/search":{"get":{"tags":["Search"],"summary":"Global search across entities","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string"},"description":"Search query"},{"in":"query","name":"limit","schema":{"type":"integer","default":5},"description":"Maximum results per category"}],"responses":{"200":{"description":"Search results"}}}},"/api/v3/settings/r-levels/{level}/users":{"get":{"tags":["Settings"],"summary":"Get all users with a specific R-Level","description":"Retrieve all users assigned a particular R-Level (approval authority tier).","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"level","required":true,"schema":{"type":"integer","minimum":1},"description":"R-Level number"}],"responses":{"200":{"description":"List of users with this R-Level"},"400":{"description":"Invalid level"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/check-approval":{"post":{"tags":["Settings"],"summary":"Check if current user can approve an amount","description":"Verify whether the authenticated user has sufficient R-Level authority to approve a transaction of the given type and amount.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","type"],"properties":{"amount":{"type":"number","description":"Amount in dollars requiring approval"},"type":{"type":"string","enum":["jobs","purchases","invoices","refunds","discounts"],"description":"Transaction type"}}}}}},"responses":{"200":{"description":"Approval authority check result"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/business-config":{"get":{"tags":["Settings"],"summary":"Get business configuration","description":"Retrieve merged business configuration combining database overrides with system defaults.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Business configuration object"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update business configuration values","description":"Batch-update one or more business configuration keys. Only provided keys are updated.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBusinessConfig"}}}},"responses":{"200":{"description":"Configuration updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/business-config/defaults":{"get":{"tags":["Settings"],"summary":"Get business configuration factory defaults","description":"Retrieve the factory-default values for all business configuration keys before any tenant customization.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Factory default configuration values"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/calendar-integrations":{"get":{"tags":["Settings"],"summary":"Get calendar integrations status and sync preferences","description":"Retrieve connection status for Google and Outlook calendar integrations along with two-way sync settings.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Calendar integration connection status and sync preferences"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update calendar sync preferences","description":"Update per-provider sync flags and tenant-wide sync settings. Only updates existing rows; no-op if a provider is not connected.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Settings saved"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/company":{"get":{"tags":["Settings"],"summary":"Get company profile","description":"Retrieve the tenant's company profile including name, logo, address, and contact information.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Company profile data"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update company profile","description":"Update the tenant's company profile details. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompanyProfile"}}}},"responses":{"200":{"description":"Company profile updated successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/settings/taxes":{"get":{"tags":["Settings"],"summary":"Get tax settings","description":"Retrieve the tenant's configured tax rates and tax collection settings.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tax settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update tax settings","description":"Update tax rate and collection configuration. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaxes"}}}},"responses":{"200":{"description":"Tax settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/settings/sms-notifications":{"get":{"tags":["Settings"],"summary":"Get SMS notification settings (EXO-130)","description":"Retrieve the tenant's customer-facing SMS preferences (technician-name display format).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"SMS notification settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update SMS notification settings (EXO-130)","description":"Set how a technician's name appears in customer SMS (full / first name / nickname / hidden). Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"SMS notification settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/settings/billing":{"get":{"tags":["Settings"],"summary":"Get billing configuration","description":"Retrieve the tenant's billing config — labor rate, default tax rate,\npayment terms, invoice prefix, late fee, materials markup. Read is\nunguarded; the default tax rate seeds new estimate/invoice tax.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Billing configuration"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update billing configuration","description":"Partially update the tenant's billing config — the primary use is the\n\"Default tax rate (%)\" control in Settings → Billing & Taxes.\nAny supplied subset of billing fields is merged over the current\nconfig. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"default_tax_rate":{"type":"number","minimum":0,"maximum":99.99},"labor_rate_per_hour":{"type":"number"},"payment_terms_days":{"type":"integer"},"invoice_prefix":{"type":"string"},"late_fee_percentage":{"type":"number"},"materials_markup_percentage":{"type":"number"},"tip":{"type":"object","description":"Gratuity configuration. Amounts are integer CENTS, never\npercentages. `attributed_to` is a closed vocabulary of\nper-job positions — Labor Code §351 makes a tip the sole\nproperty of the employee it was left for, and §350(b) reads\nany role that directs another employee's work as an \"agent\",\nso no role at or above `dispatcher` is expressible here.","additionalProperties":false,"required":["enabled","preset_amounts_cents","custom_max_cents","attributed_to"],"properties":{"enabled":{"type":"boolean"},"preset_amounts_cents":{"type":"array","maxItems":4,"items":{"type":"integer","minimum":1}},"custom_max_cents":{"type":"integer","minimum":1},"attributed_to":{"type":"string","enum":["lead_technician"]}}}}}}}},"responses":{"200":{"description":"Billing settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/settings":{"get":{"tags":["Settings"],"summary":"Get all settings","description":"Retrieve all tenant settings in a single response for initial application load.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All tenant settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update all settings","description":"Batch-update multiple settings in a single request. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAllSettings"}}}},"responses":{"200":{"description":"Settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/settings/claude/status":{"get":{"tags":["Settings"],"summary":"Get Claude AI integration status","description":"Check whether the Claude AI integration is configured and operational for this tenant.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Claude AI integration status"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/hcp/status":{"get":{"tags":["Settings"],"summary":"Get HouseCall Pro integration status","description":"Check whether the HouseCall Pro sync integration is connected and active for this tenant.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"HouseCall Pro integration status"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/hcp/set-key":{"post":{"tags":["Settings"],"summary":"Connect HouseCall Pro via API key","description":": persists the supplied API key for the tenant and\nvalidates it via a HouseCall Pro `/company` round-trip before\nwriting. Samefamily.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["apiKey"],"properties":{"apiKey":{"type":"string","minLength":1,"maxLength":512}}}}}},"responses":{"200":{"description":"API key persisted; HCP marked connected."},"400":{"description":"Invalid API key (HCP `/company` round-trip failed)."},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/ai-mode-default":{"get":{"tags":["Settings"],"summary":"Get tenant-level AI mode default (LB","description":"Returns the tenant's AUTO / ASSIST / MANUAL default mode for\nnew conversations. This is a tenant-wide product setting; the\nper-conversation override endpoint lives on the conversations\nresource.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tenant AI mode default"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/cancellation-mode":{"get":{"tags":["Settings"],"summary":"Get how the AI handles appointment cancellations","description":"Returns the tenant-level cancellation mode: `auto` (AI cancels +\nconfirms), `escalate-to-human` (AI creates a human task instead),\nor `off` (AI declines and asks the customer to call the office).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tenant cancellation mode"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update how the AI handles appointment cancellations","description":"Persist the tenant's cancellation mode. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cancellationMode"],"properties":{"cancellationMode":{"type":"string","enum":["auto","escalate-to-human","off"]}}}}}},"responses":{"200":{"description":"Cancellation mode updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/settings/public-feed-optout":{"get":{"tags":["Settings"],"summary":"Get tenant public live-feed opt-out flag (LIVE-FEED","description":"Returns whether the tenant has opted out of appearing in the\npublic login-screen live activity feed. When `true`, the tenant's\nanonymized job/invoice/estimate/call events are excluded at the\nSQL layer in the aggregator. Default: `false` (opted in).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tenant public-feed opt-out flag"},"401":{"description":"Unauthorized"},"404":{"description":"Tenant not found"}}},"put":{"tags":["Settings"],"summary":"Update tenant public live-feed opt-out flag (LIVE-FEED","description":"Persist the tenant's choice to appear (or not) in the public\nlogin-screen activity feed. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["publicFeedOptout"],"properties":{"publicFeedOptout":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Public-feed opt-out flag updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"},"404":{"description":"Tenant not found"}}}},"/api/v3/settings/prompt-variables":{"get":{"tags":["Settings"],"summary":"Get AI prompt variables grouped by category"},"patch":{"tags":["Settings"],"summary":"Batch-update AI prompt variables (admin/owner)"}},"/api/v3/settings/prompt-variables/{category}":{"get":{"tags":["Settings"],"summary":"Get AI prompt variables for one category"}},"/api/v3/settings/prompt-variables/batch":{"put":{"tags":["Settings"],"summary":"Batch-update AI prompt variables (admin/owner)"}},"/api/v3/settings/prompt-variables/{category}/{key}":{"patch":{"tags":["Settings"],"summary":"Update a single AI prompt variable (admin/owner)"}},"/api/v3/settings/r-levels/{level}":{"put":{"tags":["Settings"],"summary":"Update R-Level configuration","description":"Update the approval thresholds and configuration for a specific R-Level tier.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"level","required":true,"schema":{"type":"integer","minimum":1},"description":"R-Level number to update"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRLevel"}}}},"responses":{"200":{"description":"R-Level updated successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"R-Level not found"}}}},"/api/v3/settings/users/{id}/r-level":{"put":{"tags":["Settings"],"summary":"Assign R-Level to a user","description":"Assign an approval authority tier (R-Level) to a user, granting them transaction approval rights up to that level's threshold.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRLevel"}}}},"responses":{"200":{"description":"R-Level assigned successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}},"delete":{"tags":["Settings"],"summary":"Remove R-Level from a user","description":"Revoke the R-Level approval authority assignment from a user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"R-Level removed successfully"},"401":{"description":"Unauthorized"},"404":{"description":"R-Level assignment not found"}}},"get":{"tags":["Settings"],"summary":"Get user's R-Level assignment","description":"Retrieve the current R-Level approval authority assignment for a specific user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User R-Level assignment (null if none)"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/v3/settings/r-levels":{"get":{"tags":["Settings"],"summary":"List all R-Level configurations","description":"Retrieve all configured R-Level tiers including their approval thresholds for each transaction type.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of R-Level configurations"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/roles":{"get":{"tags":["Settings"],"summary":"List all roles","description":"Retrieve all custom and system roles defined for this tenant with their permission sets.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of roles"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Settings"],"summary":"Create a role","description":"Create a new custom role with a specific set of permissions for this tenant.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRole"}}}},"responses":{"201":{"description":"Role created successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/roles/{id}":{"get":{"tags":["Settings"],"summary":"Get role by ID","description":"Retrieve a specific role with its full permission configuration.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Role ID"}],"responses":{"200":{"description":"Role details"},"401":{"description":"Unauthorized"},"404":{"description":"Role not found"}}},"put":{"tags":["Settings"],"summary":"Update a role","description":"Update the name, description, or permissions of an existing role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Role ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRole"}}}},"responses":{"200":{"description":"Role updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Role not found"}}},"delete":{"tags":["Settings"],"summary":"Delete a role","description":"Permanently delete a custom role. Cannot delete system roles or roles currently assigned to users.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Role ID"}],"responses":{"200":{"description":"Role deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Role not found"}}}},"/api/v3/settings/permissions":{"get":{"tags":["Settings"],"summary":"Get all permissions","description":"Retrieve the complete list of available system permissions that can be assigned to roles.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of all available permissions"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/role-templates":{"get":{"tags":["Settings"],"summary":"Get role templates","description":"Retrieve predefined role templates (e.g. Field Technician, Dispatcher) that can be used as starting points for new roles.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of role templates"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/security":{"get":{"tags":["Settings"],"summary":"Get security settings","description":"Retrieve tenant security configuration including 2FA requirements, session policies, and IP allowlists.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Security settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update security settings","description":"Update tenant security policies such as 2FA enforcement, password requirements, and session timeouts.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecuritySettings"}}}},"responses":{"200":{"description":"Security settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/audit":{"get":{"tags":["Settings"],"summary":"Get audit log","description":"Retrieve paginated audit log entries for compliance and security review.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Results per page"},{"in":"query","name":"userId","schema":{"type":"string","format":"uuid"},"description":"Filter by user"},{"in":"query","name":"action","schema":{"type":"string"},"description":"Filter by action type"}],"responses":{"200":{"description":"Paginated audit log entries"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/audit/export":{"get":{"tags":["Settings"],"summary":"Export audit log","description":"Export audit log entries as CSV or JSON for external compliance systems.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Start of export date range"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"End of export date range"},{"in":"query","name":"format","schema":{"type":"string","enum":["json","csv"],"default":"json"},"description":"Export format"}],"responses":{"200":{"description":"Audit log export"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/sso":{"get":{"tags":["Settings"],"summary":"Get SSO settings","description":"Retrieve tenant-level Single Sign-On configuration including enabled providers and enforcement policy.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"SSO settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Settings"],"summary":"Update SSO settings","description":"Update Single Sign-On global settings such as SSO enforcement and default IdP.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSSOSettings"}}}},"responses":{"200":{"description":"SSO settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/sso/connections":{"get":{"tags":["Settings"],"summary":"List SSO connections","description":"Retrieve all configured SSO identity provider connections (SAML, OIDC) for this tenant.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of SSO connections"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Settings"],"summary":"Create SSO connection","description":"Add a new SSO identity provider connection with metadata URL or direct SAML/OIDC credentials.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOConnection"}}}},"responses":{"201":{"description":"SSO connection created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/sso/connections/{id}":{"put":{"tags":["Settings"],"summary":"Update SSO connection","description":"Update an existing SSO identity provider connection configuration.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"SSO connection ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOConnection"}}}},"responses":{"200":{"description":"SSO connection updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"SSO connection not found"}}},"delete":{"tags":["Settings"],"summary":"Delete SSO connection","description":"Permanently remove an SSO identity provider connection.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"SSO connection ID"}],"responses":{"200":{"description":"SSO connection deleted"},"401":{"description":"Unauthorized"},"404":{"description":"SSO connection not found"}}}},"/api/v3/settings/sso/connections/{id}/test":{"post":{"tags":["Settings"],"summary":"Test SSO connection","description":"Validate an SSO connection by attempting to fetch the IdP metadata and verifying credentials.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"SSO connection ID"}],"responses":{"200":{"description":"Test result (success or failure details)"},"401":{"description":"Unauthorized"},"404":{"description":"SSO connection not found"}}}},"/api/v3/settings/users":{"get":{"tags":["Settings"],"summary":"List team users","description":"Retrieve all users belonging to this tenant with optional filtering by role or status.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"role","schema":{"type":"string"},"description":"Filter by role"},{"in":"query","name":"status","schema":{"type":"string","enum":["active","inactive","invited"]},"description":"Filter by user status"}],"responses":{"200":{"description":"List of team users"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/users/{id}":{"get":{"tags":["Settings"],"summary":"Get user by ID","description":"Retrieve full profile and role information for a specific team member.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User details"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}},"put":{"tags":["Settings"],"summary":"Update user","description":"Update a team member's profile information or role assignment.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUser"}}}},"responses":{"200":{"description":"User updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/v3/settings/users/invite":{"post":{"tags":["Settings"],"summary":"Invite a new user","description":"Send an email invitation to a new user to join the tenant workspace.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteUser"}}}},"responses":{"200":{"description":"Invitation sent"},"400":{"description":"Validation error or email already in use"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/users/{id}/deactivate":{"post":{"tags":["Settings"],"summary":"Deactivate user","description":"Suspend a user's access to the platform without deleting their data.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User deactivated"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/v3/settings/users/{id}/reactivate":{"post":{"tags":["Settings"],"summary":"Reactivate user","description":"Restore access for a previously deactivated user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User reactivated"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/v3/settings/users/{id}/resend-invite":{"post":{"tags":["Settings"],"summary":"Resend invitation to user","description":"Resend the invitation email to a user who has not yet accepted their invite.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"Invitation resent"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/v3/settings/users/{id}/invite-delivery":{"get":{"tags":["Settings"],"summary":"Invitation delivery history for a user","description":"Recorded send attempts for this user's invitation, newest first. The recipient address is redacted and the invite token is never included; each attempt carries the provider message id so a specific send can be traced with the provider later.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"Redacted delivery attempts"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/v3/settings/users-stats":{"get":{"tags":["Settings"],"summary":"Get user statistics","description":"Retrieve aggregate statistics about team members including counts by role and status.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User statistics summary"},"401":{"description":"Unauthorized"}}}},"/api/v3/settings/users/{id}/roles":{"get":{"tags":["Settings"],"summary":"Get roles assigned to a user","description":"Retrieve all roles currently assigned to a specific user including scope (organization or property level).","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"List of role assignments"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}},"post":{"tags":["Settings"],"summary":"Assign a role to a user","description":"Assign a role to a user, optionally scoped to a specific organization or property.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role_id"],"properties":{"role_id":{"type":"string","format":"uuid"},"organization_id":{"type":"string","format":"uuid"},"property_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Role assigned successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"User or role not found"}}}},"/api/v3/settings/users/{id}/roles/{roleId}":{"delete":{"tags":["Settings"],"summary":"Remove a role from a user","description":"Revoke a specific role assignment from a user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"},{"in":"path","name":"roleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Role ID to remove"}],"responses":{"200":{"description":"Role removed successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Role assignment not found"}}}},"/api/v3/signatures/hellosign/callback":{"post":{"tags":["Signatures"],"summary":"Handle HelloSign OAuth callback","description":"Exchange OAuth code for access tokens and store integration","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","state"],"properties":{"code":{"type":"string","description":"OAuth authorization code"},"state":{"type":"string","description":"State token for CSRF protection"}}}}}},"responses":{"200":{"description":"HelloSign connected successfully"},"400":{"description":"Invalid or expired state token"}}}},"/api/v3/signatures/hellosign/disconnect":{"post":{"tags":["Signatures"],"summary":"Disconnect HelloSign integration","description":"Remove HelloSign OAuth tokens for the tenant","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"HelloSign disconnected successfully"},"404":{"description":"No HelloSign integration found"}}}},"/api/v3/signatures/hellosign/auth":{"get":{"tags":["Signatures"],"summary":"Get HelloSign OAuth URL","description":"Returns the HelloSign OAuth authorization URL for connecting a tenant","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OAuth URL returned successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"authUrl":{"type":"string"},"state":{"type":"string"}}}}}}}}}}},"/api/v3/signatures/status":{"get":{"tags":["Signatures"],"summary":"Get signature provider connection status","description":"Check whether a digital signature provider (HelloSign or DocuSign) is connected and has valid credentials.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Signature provider connection status"},"401":{"description":"Unauthorized"}}}},"/api/v3/signatures/webhooks/docusign":{"post":{"tags":["Signatures"],"summary":"DocuSign webhook endpoint","description":"Receive envelope status update events from DocuSign. Validates signature and updates document signing status.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Webhook processed"},"400":{"description":"Invalid payload or signature"}}}},"/api/v3/signatures/webhooks/hellosign":{"post":{"tags":["Signatures"],"summary":"HelloSign/Dropbox Sign webhook endpoint","description":"Receive signature event callbacks from HelloSign. Validates HMAC signature and updates document signing status.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Webhook processed (HelloSign SDK requires \"Hello API Event Received\" response)"},"400":{"description":"Invalid payload or signature"}}}},"/api/v3/tasks/{id}/checklist":{"get":{"tags":["Tasks"],"summary":"Get checklist items for a task","description":"Retrieve all checklist items for a task showing completion status.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"List of checklist items"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}},"post":{"tags":["Tasks"],"summary":"Add a checklist item to a task","description":"Add a new checklist item (sub-task) to an existing task.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Checklist item text"}}}}}},"responses":{"201":{"description":"Checklist item created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/checklist/{itemId}/complete":{"post":{"tags":["Tasks"],"summary":"Mark a checklist item as complete","description":"Mark a checklist item as completed, recording the user and timestamp.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"integer"},"description":"Checklist item ID"}],"responses":{"200":{"description":"Checklist item marked complete"},"401":{"description":"Unauthorized"},"404":{"description":"Checklist item not found"}}}},"/api/v3/tasks/{id}/checklist/{itemId}/uncomplete":{"post":{"tags":["Tasks"],"summary":"Mark a checklist item as not complete","description":"Revert a completed checklist item back to incomplete status.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"integer"},"description":"Checklist item ID"}],"responses":{"200":{"description":"Checklist item marked incomplete"},"401":{"description":"Unauthorized"},"404":{"description":"Checklist item not found"}}}},"/api/v3/tasks/{id}/checklist/{itemId}":{"delete":{"tags":["Tasks"],"summary":"Delete a checklist item","description":"Permanently remove a checklist item from a task.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"},{"in":"path","name":"itemId","required":true,"schema":{"type":"integer"},"description":"Checklist item ID"}],"responses":{"200":{"description":"Checklist item deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Checklist item not found"}}}},"/api/v3/tasks":{"post":{"tags":["Tasks"],"summary":"Create a task","description":"Create a new human task with title, type, assignee, and due date.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTask"}}}},"responses":{"201":{"description":"Task created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}},"get":{"tags":["Tasks"],"summary":"List tasks","description":"Retrieve tasks for the tenant with optional filtering by assignee, status, or type.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of tasks"},"401":{"description":"Unauthorized"}}}},"/api/v3/tasks/{id}":{"put":{"tags":["Tasks"],"summary":"Update a task","description":"Update task fields such as title, due date, priority, or description.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTask"}}}},"responses":{"200":{"description":"Task updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}},"delete":{"tags":["Tasks"],"summary":"Delete a task","description":"Hard-delete a task. Related rows in task_history, task_checklist, task_comments, task_notifications, and task_assignments cascade via ON DELETE CASCADE. Use /cancel for a soft-hide (leaves the row with status='cancelled') if you need the audit trail preserved.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Task id"}],"responses":{"200":{"description":"Task deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}},"get":{"tags":["Tasks"],"summary":"Get task by ID","description":"Retrieve a specific task by its ID.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"Task details"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/start":{"post":{"tags":["Tasks"],"summary":"Start a task","description":"Transition a task from pending to in-progress status, recording the start time.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"Task started"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/complete":{"post":{"tags":["Tasks"],"summary":"Complete a task","description":"Mark a task as completed with optional resolution notes.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteTask"}}}},"responses":{"200":{"description":"Task completed"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/cancel":{"post":{"tags":["Tasks"],"summary":"Cancel a task","description":"Cancel a task with a reason, preventing further work on it.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelTask"}}}},"responses":{"200":{"description":"Task cancelled"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/assign":{"post":{"tags":["Tasks"],"summary":"Assign a task to a user","description":"Assign or reassign a task to a specific team member.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignTask"}}}},"responses":{"200":{"description":"Task assigned"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Task or user not found"}}}},"/api/v3/tasks/{id}/reassign":{"post":{"tags":["Tasks"],"summary":"Reassign a task","description":"Reassign a task to a different team member. Actor is taken from the authenticated session.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"Task reassigned"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/comment":{"post":{"tags":["Tasks"],"summary":"Add a comment to a task","description":"Post a comment or update to the task activity feed.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskComment"}}}},"responses":{"201":{"description":"Comment added"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/stats":{"get":{"tags":["Tasks"],"summary":"Get task statistics","description":"Retrieve aggregate counts and metrics for tasks grouped by status and type.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Task statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/tasks/types":{"get":{"tags":["Tasks"],"summary":"Get task types","description":"Retrieve the list of available task type categories for creating tasks.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of task types"},"401":{"description":"Unauthorized"}}}},"/api/v3/tasks/{id}/details":{"get":{"tags":["Tasks"],"summary":"Get task details","description":"Retrieve extended task information including related entity data and linked records.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"Extended task details"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/comments":{"get":{"tags":["Tasks"],"summary":"Get task comments","description":"Retrieve all comments and activity updates for a specific task.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"List of task comments"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tasks/{id}/history":{"get":{"tags":["Tasks"],"summary":"Get task history","description":"Retrieve the complete audit trail of status changes and actions for a task.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer"},"description":"Task ID"}],"responses":{"200":{"description":"Task status history"},"401":{"description":"Unauthorized"},"404":{"description":"Task not found"}}}},"/api/v3/tenants/{id}/reactivate":{"post":{"tags":["Tenants"],"summary":"Reactivate a suspended tenant","description":"Restore full access for a tenant that was suspended due to billing or policy violations.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Tenant ID"}],"responses":{"200":{"description":"Tenant reactivated"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"Tenant not found"}}}},"/api/v3/tenants/{id}/stats":{"get":{"tags":["Tenants"],"summary":"Get tenant statistics","description":"Retrieve usage statistics for a tenant including user counts, job volumes, and subscription details.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Tenant ID"}],"responses":{"200":{"description":"Tenant statistics"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"Tenant not found"}}}},"/api/v3/tenants/{id}/feature-flags":{"post":{"tags":["Tenants"],"summary":"Set a feature flag for tenant","description":"Enable or disable a feature flag for a specific tenant for gradual rollouts or beta access.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetFeatureFlag"}}}},"responses":{"200":{"description":"Feature flag updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"}}}},"/api/v3/tenants/{id}/extend-trial":{"post":{"tags":["Tenants"],"summary":"Extend trial period for tenant","description":"Grant additional trial days to a tenant before requiring a paid subscription.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtendTrial"}}}},"responses":{"200":{"description":"Trial extended"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"}}}},"/api/v3/territories":{"get":{"tags":["Territories"],"summary":"List all territories","description":"Retrieve all service territories defined for this tenant with their geographic boundaries.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of territories"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Territories"],"summary":"Create a new territory","description":"Define a new service territory with geographic boundaries and assign technicians. Requires manager or admin role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTerritory"}}}},"responses":{"201":{"description":"Territory created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"}}}},"/api/v3/territories/{id}":{"get":{"tags":["Territories"],"summary":"Get territory by ID","description":"Retrieve a specific service territory with its boundary data and assigned technicians.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Territory ID"}],"responses":{"200":{"description":"Territory details"},"401":{"description":"Unauthorized"},"404":{"description":"Territory not found"}}},"put":{"tags":["Territories"],"summary":"Update a territory","description":"Update a territory's name, boundaries, or technician assignments. Requires manager or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Territory ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTerritory"}}}},"responses":{"200":{"description":"Territory updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"},"404":{"description":"Territory not found"}}},"delete":{"tags":["Territories"],"summary":"Delete a territory","description":"Permanently delete a service territory. Requires admin or owner role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Territory ID"}],"responses":{"200":{"description":"Territory deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"},"404":{"description":"Territory not found"}}}},"/api/v3/territories/expand":{"post":{"tags":["Territories"],"summary":"Expand a picked place to its ZIP codes","description":"Server-side expansion of a service-area selection into US ZIP codes. Accepts a city + state, a single ZIP, or a ZIP + radius (miles), and returns the deduplicated ZIP set plus the matched city names. Pure compute — does not persist anything.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["kind","city","state"],"properties":{"kind":{"type":"string","enum":["city"]},"city":{"type":"string"},"state":{"type":"string","description":"Two-letter US state code"}}},{"type":"object","required":["kind","zip"],"properties":{"kind":{"type":"string","enum":["zip"]},"zip":{"type":"string"}}},{"type":"object","required":["kind","zip","radiusMiles"],"properties":{"kind":{"type":"string","enum":["radius"]},"zip":{"type":"string"},"radiusMiles":{"type":"number","minimum":1,"maximum":100}}}]}}}},"responses":{"200":{"description":"Expanded ZIP set"},"400":{"description":"Validation error (invalid state/zip/radius or result too large)"},"401":{"description":"Unauthorized"}}}},"/api/v3/territories/stats":{"get":{"tags":["Territories"],"summary":"Get territory statistics","description":"Retrieve aggregate statistics about territories including coverage areas and technician distribution.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Territory statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/territories/by-zip/{zipCode}":{"get":{"tags":["Territories"],"summary":"Find territories by ZIP code","description":"Look up which service territories cover a given ZIP code for lead routing.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"zipCode","required":true,"schema":{"type":"string"},"description":"US ZIP code"}],"responses":{"200":{"description":"Matching territories"},"401":{"description":"Unauthorized"}}}},"/api/v3/territories/technician/{technicianId}":{"get":{"tags":["Territories"],"summary":"Get territories for a technician","description":"Retrieve all service territories assigned to a specific field technician.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"technicianId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Technician user ID"}],"responses":{"200":{"description":"Territories assigned to technician"},"401":{"description":"Unauthorized"},"404":{"description":"Technician not found"}}}},"/api/v3/thumbtack":{"put":{"tags":["Thumbtack"],"summary":"Update Thumbtack settings","description":"Update Thumbtack integration configuration including auto-respond and sync preferences.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThumbTackSettings"}}}},"responses":{"200":{"description":"Settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/sync":{"post":{"tags":["Thumbtack"],"summary":"Sync Thumbtack data","description":"Trigger a manual sync of leads and negotiations from Thumbtack.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sync initiated"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/negotiations/{negotiationId}/status":{"post":{"tags":["Thumbtack"],"summary":"Update negotiation status","description":"Update the status of a Thumbtack lead negotiation (e.g. accept, decline).","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"negotiationId","required":true,"schema":{"type":"string"},"description":"Thumbtack negotiation ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateThumbTackStatus"}}}},"responses":{"200":{"description":"Status updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Negotiation not found"}}}},"/api/v3/thumbtack/negotiations/{negotiationId}/send-message":{"post":{"tags":["Thumbtack"],"summary":"Send message in a negotiation","description":"Send a message to the customer through the Thumbtack negotiation thread.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"negotiationId","required":true,"schema":{"type":"string"},"description":"Thumbtack negotiation ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendThumbTackMessage"}}}},"responses":{"200":{"description":"Message sent"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Negotiation not found"}}}},"/api/v3/thumbtack/negotiations/{negotiationId}/create-job":{"post":{"tags":["Thumbtack"],"summary":"Create a job from a negotiation","description":"Convert a Thumbtack lead negotiation into a scheduled job in the system.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"negotiationId","required":true,"schema":{"type":"string"},"description":"Thumbtack negotiation ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJobFromNegotiation"}}}},"responses":{"201":{"description":"Job created from negotiation"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Negotiation not found"}}}},"/api/v3/thumbtack/pull-lead":{"post":{"tags":["Thumbtack"],"summary":"Pull a lead from Thumbtack","description":"Manually fetch a specific lead from Thumbtack by lead ID.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PullLead"}}}},"responses":{"200":{"description":"Lead pulled successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/test-webhook":{"post":{"tags":["Thumbtack"],"summary":"Test Thumbtack webhook configuration","description":"Verify Thumbtack webhook URL is reachable and correctly configured. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook test result"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/dev/simulate-webhook":{"post":{"tags":["Thumbtack"],"summary":"Simulate a Thumbtack webhook (dev only)","description":"Simulate an inbound Thumbtack webhook event for development and testing purposes.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook simulated"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/retry-webhooks":{"post":{"tags":["Thumbtack"],"summary":"Retry failed Thumbtack webhooks","description":"Re-process previously failed Thumbtack webhook deliveries. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Retry initiated"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/cleanup-stale-webhooks":{"post":{"tags":["Thumbtack"],"summary":"Purge orphan webhooks on this tenant's Thumbtack business","description":"Lists every webhook Thumbtack has registered for this tenant's\nbusiness and deletes any whose URL points at our domain but\ncarries a tenant-id segment we no longer track in\n`thumbtack_integrations` (PINT-2887 ghost-cleanup). Sibling\ntenants on the same TT business and webhooks on other domains\nare left alone. The caller's own webhook is NOT touched — use\n`/retry-webhooks` for a full re-register. Requires admin or\nowner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Cleanup complete — returns kept and deleted URL lists"},"400":{"description":"No API-mode Thumbtack integration found"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/connect":{"post":{"tags":["Thumbtack"],"summary":"Connect Thumbtack OAuth integration","description":"Initiate the Thumbtack OAuth connection flow for this tenant. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OAuth connection initiated"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/manual-setup":{"post":{"tags":["Thumbtack"],"summary":"Start manual Thumbtack setup","description":"Configure Thumbtack integration manually with an API key instead of OAuth. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThumbTackManualSetup"}}}},"responses":{"200":{"description":"Manual setup initiated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/manual-setup/complete":{"post":{"tags":["Thumbtack"],"summary":"Complete manual Thumbtack setup","description":"Finalize the manual Thumbtack API integration after providing credentials. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThumbTackManualSetupComplete"}}}},"responses":{"200":{"description":"Setup completed"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/select-business":{"post":{"tags":["Thumbtack"],"summary":"Select Thumbtack business account","description":"Choose which Thumbtack business account to link when multiple accounts are available. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectThumbTackBusiness"}}}},"responses":{"200":{"description":"Business account selected"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/disconnect":{"post":{"tags":["Thumbtack"],"summary":"Disconnect Thumbtack integration","description":"Remove the Thumbtack OAuth connection and stop syncing leads. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Thumbtack disconnected"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/test":{"post":{"tags":["Thumbtack"],"summary":"Test Thumbtack connection","description":"Verify the current Thumbtack API credentials are valid and the connection is active.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection test result"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/upgrade-to-api":{"post":{"tags":["Thumbtack"],"summary":"Upgrade Thumbtack integration to API mode","description":"Upgrade from webhook-based integration to direct API mode for richer lead data. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Upgraded to API mode"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/status":{"get":{"tags":["Thumbtack"],"summary":"Get Thumbtack connection status","description":"Check whether the Thumbtack integration is connected and the current connection mode.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Thumbtack connection status"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/webhook-info":{"get":{"tags":["Thumbtack"],"summary":"Get webhook URL and setup instructions","description":"Retrieve the Thumbtack webhook URL and configuration instructions for manual setup. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook URL and setup guide"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/thumbtack/pending-businesses":{"get":{"tags":["Thumbtack"],"summary":"Get pending business accounts for OAuth selection","description":"Retrieve available Thumbtack business accounts awaiting selection during OAuth connection flow.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of pending business accounts"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/stats":{"get":{"tags":["Thumbtack"],"summary":"Get Thumbtack integration statistics","description":"Retrieve aggregate metrics for the Thumbtack integration including lead counts and conversion rates.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Thumbtack integration statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/sync-history":{"get":{"tags":["Thumbtack"],"summary":"Get Thumbtack sync history","description":"Retrieve the history of Thumbtack data sync operations with status and results.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","maximum":100},"description":"Number of sync history entries to return"},{"in":"query","name":"offset","schema":{"type":"integer"},"description":"Pagination offset"}],"responses":{"200":{"description":"Sync history entries"},"401":{"description":"Unauthorized"}}}},"/api/v3/thumbtack/by-conversation/{conversationId}":{"get":{"tags":["Thumbtack"],"summary":"Get Thumbtack lead by conversation ID","description":"Look up a Thumbtack lead record using the Thumbtack conversation identifier.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"conversationId","required":true,"schema":{"type":"string"},"description":"Thumbtack conversation ID"}],"responses":{"200":{"description":"Thumbtack lead details"},"401":{"description":"Unauthorized"},"404":{"description":"Lead not found"}}}},"/api/v3/thumbtack/messages/{negotiationId}":{"get":{"tags":["Thumbtack"],"summary":"Get messages for a negotiation","description":"Retrieve message history for a Thumbtack lead negotiation thread.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"negotiationId","required":true,"schema":{"type":"string"},"description":"Thumbtack negotiation ID"},{"in":"query","name":"limit","schema":{"type":"integer","maximum":100},"description":"Maximum messages to return"}],"responses":{"200":{"description":"Negotiation messages"},"401":{"description":"Unauthorized"},"404":{"description":"Negotiation not found"}}}},"/api/v3/thumbtack/negotiations/{negotiationId}":{"get":{"tags":["Thumbtack"],"summary":"Get a negotiation by ID","description":"Retrieve full details of a specific Thumbtack lead negotiation.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"negotiationId","required":true,"schema":{"type":"string"},"description":"Thumbtack negotiation ID"}],"responses":{"200":{"description":"Negotiation details"},"401":{"description":"Unauthorized"},"404":{"description":"Negotiation not found"}}}},"/api/v3/thumbtack/negotiations/{negotiationId}/messages-from-tt":{"get":{"tags":["Thumbtack"],"summary":"Get live messages from Thumbtack for a negotiation","description":"Fetch the latest messages directly from the Thumbtack API for a negotiation (bypassing local cache).","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"negotiationId","required":true,"schema":{"type":"string"},"description":"Thumbtack negotiation ID"}],"responses":{"200":{"description":"Live messages from Thumbtack"},"401":{"description":"Unauthorized"},"404":{"description":"Negotiation not found"}}}},"/api/v3/time-tracking/break/start":{"post":{"tags":["Time Tracking"],"summary":"Start a break","description":"Begin a timed break period for the authenticated user. User must be clocked in.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","description":"Optional break notes"}}}}}},"responses":{"200":{"description":"Break started"},"400":{"description":"Not clocked in or already on break"},"401":{"description":"Unauthorized"}}}},"/api/v3/time-tracking/break/end":{"post":{"tags":["Time Tracking"],"summary":"End a break","description":"End the current break period and resume the work timer for the authenticated user.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","description":"Optional end-of-break notes"}}}}}},"responses":{"200":{"description":"Break ended"},"400":{"description":"Not on break"},"401":{"description":"Unauthorized"}}}},"/api/v3/time-tracking/clock-in":{"post":{"tags":["Time Tracking"],"summary":"Clock in for the day","description":"Start the work timer for the authenticated user. Optionally links to a specific job and validates GPS location.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClockEvent"}}}},"responses":{"200":{"description":"Clocked in successfully"},"400":{"description":"Already clocked in or validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/time-tracking/clock-out":{"post":{"tags":["Time Tracking"],"summary":"Clock out for the day","description":"Stop the work timer for the authenticated user, recording total hours worked.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClockEvent"}}}},"responses":{"200":{"description":"Clocked out successfully"},"400":{"description":"Not clocked in or validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/time-tracking/status":{"get":{"tags":["Time Tracking"],"summary":"Get current clock status","description":"Returns whether the user is clocked in, on break, and today's total hours worked.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current clock status and today's hours"},"401":{"description":"Unauthorized"}}}},"/api/v3/time-tracking/payroll/export":{"get":{"tags":["Time Tracking"],"summary":"Export payroll data for a date range","description":"Export timesheet data for payroll processing in JSON or CSV format. Requires manager or admin role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"startDate","required":true,"schema":{"type":"string","format":"date"},"description":"Start of payroll period"},{"in":"query","name":"endDate","required":true,"schema":{"type":"string","format":"date"},"description":"End of payroll period"},{"in":"query","name":"userIds","schema":{"type":"string"},"description":"Comma-separated list of user IDs to filter (all users if omitted)"},{"in":"query","name":"format","schema":{"type":"string","enum":["json","csv"],"default":"json"},"description":"Export format"}],"responses":{"200":{"description":"Payroll data (JSON or CSV file)"},"400":{"description":"Invalid date range"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role"}}}},"/api/v3/time-tracking/users/{userId}/timesheet/weekly":{"get":{"tags":["Time Tracking"],"summary":"Get specific user's weekly timesheet (managers only)","description":"Retrieve weekly timesheet for any user. Users can view their own; managers can view any team member.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"},{"in":"query","name":"weekStart","required":true,"schema":{"type":"string","format":"date"},"description":"Start date of the week (Monday)"}],"responses":{"200":{"description":"Weekly timesheet with daily breakdown"},"400":{"description":"Invalid week start date"},"401":{"description":"Unauthorized"},"403":{"description":"Cannot view another user's timesheet without manager role"}}}},"/api/v3/time-tracking/users/{userId}/timesheet/{date}":{"get":{"tags":["Time Tracking"],"summary":"Get specific user's daily timesheet (managers only)","description":"Retrieve a user's detailed time entries for a specific date including clock in/out and breaks.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"},{"in":"path","name":"date","required":true,"schema":{"type":"string","format":"date"},"description":"Date (YYYY-MM-DD)"}],"responses":{"200":{"description":"Daily timesheet entries"},"400":{"description":"Invalid date format"},"401":{"description":"Unauthorized"},"403":{"description":"Cannot view another user's timesheet without manager role"}}}},"/api/v3/twilio-phones/owned":{"get":{"tags":["Twilio Phones"],"summary":"List all owned phone numbers from Twilio","description":"Retrieve all phone numbers currently provisioned in the tenant's Twilio account.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of owned Twilio phone numbers"},"401":{"description":"Unauthorized"}}}},"/api/v3/twilio-phones/owned/database":{"get":{"tags":["Twilio Phones"],"summary":"List tenant phone numbers from database","description":"Retrieve phone numbers stored in the local database (may differ from live Twilio data).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of phone numbers from database"},"401":{"description":"Unauthorized"}}}},"/api/v3/twilio-phones/owned/{sid}":{"get":{"tags":["Twilio Phones"],"summary":"Get a phone number by Twilio SID","description":"Retrieve details for a specific phone number using its Twilio SID.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string","pattern":"^PN[0-9a-fA-F]{32}$"},"description":"Twilio Phone Number SID"}],"responses":{"200":{"description":"Phone number details"},"401":{"description":"Unauthorized"},"404":{"description":"Phone number not found"}}},"delete":{"tags":["Twilio Phones"],"summary":"Release a phone number","description":"Release a phone number from the Twilio account, removing it permanently. Requires admin or owner role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string","pattern":"^PN[0-9a-fA-F]{32}$"},"description":"Twilio Phone Number SID"}],"responses":{"200":{"description":"Phone number released"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"},"404":{"description":"Phone number not found"}}}},"/api/v3/twilio-phones/owned/sync":{"post":{"tags":["Twilio Phones"],"summary":"Sync Twilio numbers with database","description":"Synchronize phone numbers from Twilio API into the local database. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sync completed"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/twilio-phones/purchase":{"post":{"tags":["Twilio Phones"],"summary":"Purchase a phone number","description":"Purchase a specific phone number from Twilio by providing the desired number. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchasePhoneNumber"}}}},"responses":{"201":{"description":"Phone number purchased"},"400":{"description":"Number unavailable or validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/twilio-phones/purchase/quick":{"post":{"tags":["Twilio Phones"],"summary":"Quick purchase a phone number","description":"Automatically select and purchase the first available phone number matching search criteria. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuickPurchasePhoneNumber"}}}},"responses":{"201":{"description":"Phone number purchased"},"400":{"description":"No numbers available or validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/twilio-phones/purchase/toll-free":{"post":{"tags":["Twilio Phones"],"summary":"Purchase a toll-free phone number","description":"Purchase a toll-free (800/888/etc.) phone number from Twilio. Requires admin or owner role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuickPurchasePhoneNumber"}}}},"responses":{"201":{"description":"Toll-free number purchased"},"400":{"description":"No toll-free numbers available or validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/twilio-phones/search/local":{"get":{"tags":["Twilio Phones"],"summary":"Search available local phone numbers","description":"Search Twilio's inventory for available local phone numbers matching area code or region criteria.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"areaCode","schema":{"type":"string"},"description":"Area code to search"},{"in":"query","name":"contains","schema":{"type":"string"},"description":"Pattern to match in phone number digits"}],"responses":{"200":{"description":"List of available local numbers"},"401":{"description":"Unauthorized"}}}},"/api/v3/twilio-phones/search/toll-free":{"get":{"tags":["Twilio Phones"],"summary":"Search available toll-free numbers","description":"Search Twilio's inventory for available toll-free (800/888/etc.) phone numbers.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"contains","schema":{"type":"string"},"description":"Pattern to match in the phone number"}],"responses":{"200":{"description":"List of available toll-free numbers"},"401":{"description":"Unauthorized"}}}},"/api/v3/twilio-phones/search/by-area/{areaCode}":{"get":{"tags":["Twilio Phones"],"summary":"Search phone numbers by area code","description":"Convenience endpoint to find available phone numbers for a specific US area code.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"areaCode","required":true,"schema":{"type":"string"},"description":"3-digit US area code"}],"responses":{"200":{"description":"Available phone numbers for the area code"},"401":{"description":"Unauthorized"}}}},"/api/v3/twilio-phones/webhooks/{sid}":{"put":{"tags":["Twilio Phones"],"summary":"Configure webhooks for a phone number","description":"Update the inbound SMS and voice webhook URLs for a phone number in Twilio. Requires admin or owner role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string"},"description":"Twilio Phone Number SID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigureWebhooks"}}}},"responses":{"200":{"description":"Webhooks configured"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}},"delete":{"tags":["Twilio Phones"],"summary":"Remove webhooks for a phone number","description":"Clear all webhook URLs from a phone number in Twilio. Requires admin or owner role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string"},"description":"Twilio Phone Number SID"}],"responses":{"200":{"description":"Webhooks removed"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/twilio-phones/webhooks/{sid}/exoserva":{"post":{"tags":["Twilio Phones"],"summary":"Configure Exoserva webhook for a phone number","description":"Point a phone number's Twilio webhooks to the Exoserva platform endpoints. Requires admin or owner role.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sid","required":true,"schema":{"type":"string"},"description":"Twilio Phone Number SID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExoservaWebhookConfig"}}}},"responses":{"200":{"description":"Exoserva webhooks configured"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/twilio-phones/webhooks/defaults":{"get":{"tags":["Twilio Phones"],"summary":"Get default Exoserva webhook URLs","description":"Retrieve the pre-configured Exoserva webhook URLs for inbound SMS and voice. Requires admin or owner role.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Default Exoserva webhook URL configuration"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires admin/owner)"}}}},"/api/v3/users":{"get":{"tags":["Users"],"summary":"List users","description":"List users in current tenant with optional pagination and role filters.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Results per page"}],"responses":{"200":{"description":"Paginated list of users"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Users"],"summary":"Create user","description":"Create a new user account in the tenant with specified role and profile information.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUser"}}}},"responses":{"201":{"description":"User created"},"400":{"description":"Validation error or email already exists"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"}}}},"/api/v3/users/{id}":{"get":{"tags":["Users"],"summary":"Get user by ID","description":"Retrieve a specific user's profile by their UUID.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User profile"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}},"patch":{"tags":["Users"],"summary":"Update user","description":"Partially update a user's profile fields.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUser"}}}},"responses":{"200":{"description":"User updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"User not found"}}},"delete":{"tags":["Users"],"summary":"Delete user","description":"Deactivate (soft delete) a user, revoking their access without deleting their data.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User deactivated"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"User not found"}}}},"/api/v3/users/me":{"get":{"tags":["Users"],"summary":"Get current user","description":"Get current user profile including roles, permissions, and tenant context.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current user profile"},"401":{"description":"Unauthorized"}}},"patch":{"tags":["Users"],"summary":"Update current user","description":"Update the authenticated user's own profile fields such as name, phone, and avatar.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMe"}}}},"responses":{"200":{"description":"Profile updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/users/me/security":{"get":{"tags":["Users"],"summary":"Get current user security status","description":"Retrieve the authenticated user's security settings including 2FA enrollment status and password information.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User security status"},"401":{"description":"Unauthorized"}}}},"/api/v3/users/me/permissions":{"get":{"tags":["Users"],"summary":"Get current user element permissions","description":"Retrieve the authenticated user's fine-grained UI element permissions used by PermissionGate components.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Map of permission keys to boolean values"},"401":{"description":"Unauthorized"}}}},"/api/v3/users/me/password":{"put":{"tags":["Users"],"summary":"Change current user password","description":"Change the current user's password (requires current password)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["current_password","new_password"],"properties":{"current_password":{"type":"string"},"new_password":{"type":"string","minLength":8}}}}}}}},"/api/v3/users/{id}/reset-password":{"post":{"tags":["Users"],"summary":"Trigger a password reset email for a user (admin)","description":"Sends a single-use password reset link to the target user's email. Tenant-scoped — admin must belong to the same tenant as the target user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Target user ID"}],"responses":{"200":{"description":"Reset link accepted by the email provider. `data.delivery` carries the provider name and its non-secret message id."},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"User not found in tenant"},"502":{"description":"Reset link was not delivered — the provider refused it or none is configured. No reset email exists; the user's previous link is untouched."}}}},"/api/v3/users/preferences":{"get":{"tags":["Users"],"summary":"Get current user preferences","description":"Retrieve UI and notification preferences for the authenticated user.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User preferences"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Users"],"summary":"Update current user preferences","description":"Update UI and notification preferences for the authenticated user (merges with existing values).","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePreferences"}}}},"responses":{"200":{"description":"Preferences updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/users/{id}/roles":{"get":{"tags":["Users"],"summary":"Get user roles","description":"Retrieve all roles assigned to a specific user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"List of user roles"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}},"post":{"tags":["Users"],"summary":"Assign role to user","description":"Assign a role to a user with optional scope to organization or property.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRole"}}}},"responses":{"201":{"description":"Role assigned"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"User or role not found"}}}},"/api/v3/users/{id}/roles/{roleId}":{"delete":{"tags":["Users"],"summary":"Remove role from user","description":"Revoke a specific role assignment from a user.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"},{"in":"path","name":"roleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Role assignment ID"}],"responses":{"200":{"description":"Role removed"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"Role assignment not found"}}}},"/api/v3/users/search":{"get":{"tags":["Users"],"summary":"Search users","description":"Full-text search across users in the tenant by name or email.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string","minLength":2},"description":"Search query (min 2 characters)"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Maximum results to return"}],"responses":{"200":{"description":"List of matching users"},"401":{"description":"Unauthorized"}}}},"/api/accounts/{accountId}/contacts":{"get":{"tags":["Accounts"],"summary":"List contacts for an account","description":"Retrieve all contacts associated with a specific account, with optional filtering and sorting support.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"responses":{"200":{"description":"List of contacts for the account"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:read permission"},"404":{"description":"Account not found"}}},"post":{"tags":["Accounts"],"summary":"Create a contact for an account","description":"Add a new contact (person) to an existing account. Contacts represent individuals associated with a business account.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"responses":{"201":{"description":"Contact created successfully"},"400":{"description":"Validation error — required contact fields missing or invalid"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:update permission"},"404":{"description":"Account not found"}}}},"/api/accounts/{accountId}/contacts/{contactId}":{"get":{"tags":["Accounts"],"summary":"Get a contact by ID","description":"Retrieve a single contact by its ID within the context of a specific account.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"},{"in":"path","name":"contactId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Contact UUID"}],"responses":{"200":{"description":"Contact details"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:read permission"},"404":{"description":"Contact or account not found"}}},"patch":{"tags":["Accounts"],"summary":"Update a contact","description":"Partially update a contact's information such as name, phone, email, or role within the account.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"},{"in":"path","name":"contactId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Contact UUID"}],"responses":{"200":{"description":"Contact updated successfully"},"400":{"description":"Validation error — invalid contact data"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:update permission"},"404":{"description":"Contact or account not found"}}},"delete":{"tags":["Accounts"],"summary":"Delete a contact from an account","description":"Permanently remove a contact from an account. Deleting the primary contact requires another contact to be set as primary first.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"},{"in":"path","name":"contactId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Contact UUID"}],"responses":{"200":{"description":"Contact deleted successfully"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:update permission"},"404":{"description":"Contact or account not found"}}}},"/api/accounts/{accountId}/contacts/{contactId}/set-primary":{"post":{"tags":["Accounts"],"summary":"Set a contact as the primary contact","description":"Designate a specific contact as the primary contact for the account. Only one contact can be primary at a time.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"},{"in":"path","name":"contactId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Contact UUID to set as primary"}],"responses":{"200":{"description":"Contact set as primary successfully"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:update permission"},"404":{"description":"Contact or account not found"}}}},"/api/accounts/{accountId}/contacts/{contactId}/ai-mode-override":{"patch":{"tags":["Accounts"],"summary":"Set or clear the per-contact AI mode override","description":"AI gating audit PR-7a — pin this contact's AI handling to one of\n`auto` | `assist` | `manual` (or `null` to inherit from the\nresolver chain). A non-NULL override wins over the feature-setting\nmode and the tenant default, but still loses to the per-conversation\noverride and the AI Hub kill switch (`ai_global_settings.ai_enabled`\n= false) / feature gate (`ai_feature_settings.is_enabled` = false).","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"contactId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Override set or cleared"},"400":{"description":"Invalid mode literal"},"404":{"description":"Contact not found"}}}},"/api/accounts/{accountId}/contacts/{contactId}/effective-ai-mode":{"get":{"tags":["Accounts"],"summary":"Resolve the effective AI mode for a contact","description":"AI gating audit PR-7a — folds the full precedence chain (kill\nswitch, feature gate, conversation override, contact override,\nfeature setting, tenant default, legacy mode, fallback) and\nreturns `{ mode, source }` so the UI can render both the\nactive dropdown value AND the source badge\n(\"MANUAL (contact preference)\" vs \"ASSIST (tenant default)\"). No `conversationId` query parameter — the contact-detail surface\ndoes not bind to a single conversation. Per-conversation override\nresolution lives on `PUT /api/conversations/:id/mode-override`\n(LB and its sibling effective-mode endpoint.","parameters":[{"in":"path","name":"accountId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"contactId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ mode, source } resolution"},"404":{"description":"Contact not found"}}}},"/api/accounts/stats":{"get":{"tags":["Accounts"],"summary":"Get account statistics","description":"Retrieve aggregate statistics for accounts in the tenant, including total counts by status, recent activity, and growth metrics.","responses":{"200":{"description":"Account statistics summary"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:read permission"}}}},"/api/accounts/lead-sources":{"get":{"tags":["Accounts"],"summary":"Get unique lead sources for accounts","description":"Retrieve the distinct list of lead source values used across all accounts. Useful for populating filter dropdowns.","responses":{"200":{"description":"List of unique lead source values"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:list permission"}}}},"/api/accounts/search":{"get":{"tags":["Accounts"],"summary":"Search accounts by query","description":"Full-text search across account names, contacts, and metadata. Use this for typeahead/autocomplete when linking accounts to jobs or invoices.","parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string"},"description":"Search query string"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of results to return"}],"responses":{"200":{"description":"Matching accounts"},"400":{"description":"Validation error — search query missing or invalid"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:list permission"}}}},"/api/accounts/check-duplicates":{"get":{"tags":["Accounts"],"summary":"Check for potential duplicate accounts","description":"Returns accounts matching the provided name, email, or normalized phone number. At least one of these query parameters is required.","parameters":[{"in":"query","name":"name","schema":{"type":"string"},"description":"Account name to match (case-insensitive exact match)"},{"in":"query","name":"email","schema":{"type":"string"},"description":"Email address to match (case-insensitive exact match)"},{"in":"query","name":"phone","schema":{"type":"string"},"description":"Phone number to match (normalized to digits only)"},{"in":"query","name":"exclude_id","schema":{"type":"string","format":"uuid"},"description":"Exclude this account ID from results (e.g. when editing existing)"}],"responses":{"200":{"description":"Matching accounts with match_reason annotation"},"400":{"description":"Validation error — at least one of name/email/phone required"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:list permission"}}}},"/api/accounts/{id}/tags":{"post":{"tags":["Accounts"],"summary":"Add a tag to an account","description":"Add a string tag to an account for categorization and filtering purposes. Tags are tenant-scoped and case-sensitive.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"}],"responses":{"200":{"description":"Updated account with new tag"},"400":{"description":"Validation error — tag value missing or invalid"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:update permission"},"404":{"description":"Account not found"}}}},"/api/accounts/{id}/tags/{tag}":{"delete":{"tags":["Accounts"],"summary":"Remove a tag from an account","description":"Remove a specific tag from an account. If the tag does not exist on the account, the request still returns 200.","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Account UUID"},{"in":"path","name":"tag","required":true,"schema":{"type":"string"},"description":"Tag value to remove"}],"responses":{"200":{"description":"Updated account without the removed tag"},"401":{"description":"Unauthorized — missing or invalid JWT token"},"403":{"description":"Forbidden — requires accounts:update permission"},"404":{"description":"Account not found"}}}},"/api/v3/vendors/{vendorId}/coi":{"post":{"tags":["Vendor COI"],"summary":"Upload COI for a vendor","description":"Upload a Certificate of Insurance (COI) document for a vendor for compliance tracking.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadCOI"}}}},"responses":{"201":{"description":"COI uploaded"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Vendor not found"}}},"get":{"tags":["Vendor COI"],"summary":"Get vendor COI records","description":"Retrieve all Certificate of Insurance records for a vendor with optional status and coverage type filtering.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated status filter (e.g. active,expired)"},{"in":"query","name":"coverage_type","schema":{"type":"string"},"description":"Filter by coverage type"},{"in":"query","name":"include_expired","schema":{"type":"boolean"},"description":"Include expired COIs in results"}],"responses":{"200":{"description":"List of COI records"},"401":{"description":"Unauthorized"},"404":{"description":"Vendor not found"}}}},"/api/v3/vendors/{vendorId}/coi/manual":{"post":{"tags":["Vendor COI"],"summary":"Manually create a COI record for a vendor","description":"Create a COI record by manually entering policy details without document upload.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCOI"}}}},"responses":{"201":{"description":"COI record created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Vendor not found"}}}},"/api/v3/vendors/{vendorId}/coi/{coiId}":{"put":{"tags":["Vendor COI"],"summary":"Update a vendor COI record","description":"Update the policy details or expiration date for an existing COI record.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"},{"in":"path","name":"coiId","required":true,"schema":{"type":"string","format":"uuid"},"description":"COI record ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCOI"}}}},"responses":{"200":{"description":"COI record updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"COI record not found"}}},"delete":{"tags":["Vendor COI"],"summary":"Delete a vendor COI record","description":"Permanently remove a COI record for a vendor.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"},{"in":"path","name":"coiId","required":true,"schema":{"type":"string","format":"uuid"},"description":"COI record ID"}],"responses":{"200":{"description":"COI record deleted"},"401":{"description":"Unauthorized"},"404":{"description":"COI record not found"}}},"get":{"tags":["Vendor COI"],"summary":"Get a specific COI record","description":"Retrieve full details for a specific Certificate of Insurance record.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"},{"in":"path","name":"coiId","required":true,"schema":{"type":"string","format":"uuid"},"description":"COI record ID"}],"responses":{"200":{"description":"COI record details"},"401":{"description":"Unauthorized"},"404":{"description":"COI record not found"}}}},"/api/v3/vendors/{vendorId}/coi/compliance":{"get":{"tags":["Vendor COI"],"summary":"Check vendor COI compliance","description":"Evaluate whether a vendor has current, valid COI coverage meeting all required thresholds.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"}],"responses":{"200":{"description":"COI compliance status"},"401":{"description":"Unauthorized"},"404":{"description":"Vendor not found"}}}},"/api/v3/vendors/{id}/intelligence":{"get":{"tags":["Vendor Intelligence"],"summary":"Get AI performance analysis for a vendor","description":"Retrieve Claude AI-generated performance analysis for a vendor based on historical job data and metrics.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"},{"in":"query","name":"period","schema":{"type":"string","enum":["30d","90d","1y"]},"description":"Analysis period"}],"responses":{"200":{"description":"AI performance analysis"},"401":{"description":"Unauthorized"},"404":{"description":"Vendor not found"}}}},"/api/v3/vendors/{id}/predictions":{"get":{"tags":["Vendor Intelligence"],"summary":"Get performance predictions for a vendor","description":"Get AI-generated performance forecasts for a vendor including risk assessment and trend projections.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Vendor ID"},{"in":"query","name":"period","schema":{"type":"string","enum":["30d","90d","1y"]},"description":"Prediction period"}],"responses":{"200":{"description":"Performance predictions"},"401":{"description":"Unauthorized"},"404":{"description":"Vendor not found"}}}},"/api/v3/vendors/intelligence/recommend":{"post":{"tags":["Vendor Intelligence"],"summary":"Get AI vendor recommendations for a job","description":"Use Claude AI to recommend the best-matching vendors for a specific job based on skills, location, availability, and past performance.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRecommendRequest"}}}},"responses":{"200":{"description":"Ranked vendor recommendations"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/vendors/intelligence/insights":{"get":{"tags":["Vendor Intelligence"],"summary":"Get network-wide AI vendor insights","description":"Retrieve AI-generated insights about the entire vendor network including performance trends, risk flags, and optimization opportunities.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"category","schema":{"type":"string"},"description":"Filter insights by category"}],"responses":{"200":{"description":"Network-wide vendor insights"},"401":{"description":"Unauthorized"}}}},"/api/v3/vendors/intelligence/cache":{"delete":{"tags":["Vendor Intelligence"],"summary":"Clear cached vendor intelligence data","description":"Invalidate cached AI analysis results to force fresh computation on next request.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearIntelligenceCache"}}}},"responses":{"200":{"description":"Cache cleared"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"}}}},"/api/v3/vendor-portal/stats":{"get":{"tags":["Vendor Portal"],"summary":"Get vendor statistics","description":"Retrieve order and invoice statistics for the authenticated vendor user including counts by status and revenue totals.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Vendor order and invoice statistics"},"401":{"description":"Unauthorized"}}}},"/api/voice/analytics":{"get":{"tags":["Voice"],"summary":"Get voice analytics summary","description":"Get tiered response system metrics and analytics summary","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"days","schema":{"type":"integer","default":7,"minimum":1,"maximum":90},"description":"Number of days to include"}],"responses":{"200":{"description":"Analytics summary"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/voice/analytics/today":{"get":{"tags":["Voice"],"summary":"Get today's voice metrics","description":"Get real-time tier metrics for the current day","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Today's metrics"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/voice/analytics/logs":{"get":{"tags":["Voice"],"summary":"Get voice message logs","description":"Get recent voice message processing logs","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","default":50,"maximum":200},"description":"Maximum number of logs to return"}],"responses":{"200":{"description":"Voice message logs"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v3/voice/calls":{"post":{"tags":["Voice"],"summary":"Record an outbound call","description":"Creates a voice call record when a browser-initiated outbound call is made via VoIP dialer."}},"/api/v3/voice/calls/{id}/end":{"patch":{"tags":["Voice"],"summary":"Update call status on completion"}},"/api/v3/voice/status":{"get":{"tags":["Voice"],"summary":"Get voice agent status","description":"Check the operational status of the voice AI agent and Twilio configuration.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Voice agent status and configuration"},"401":{"description":"Unauthorized"}}}},"/api/v3/voice/settings":{"get":{"tags":["Voice"],"summary":"Get voice settings","description":"Retrieve voice agent configuration including greeting scripts, escalation rules, and transfer settings.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Voice settings"},"401":{"description":"Unauthorized"}}},"put":{"tags":["Voice"],"summary":"Update voice settings","description":"Update voice agent configuration. Requires manager or admin role.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVoiceSettings"}}}},"responses":{"200":{"description":"Voice settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient role (requires manager/admin/owner)"}}}},"/api/v3/weather/current":{"get":{"tags":["Weather"],"summary":"Get current weather conditions","description":"Retrieve real-time weather data for a GPS coordinate to inform field scheduling decisions.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"lat","required":true,"schema":{"type":"number","format":"float"},"description":"Latitude coordinate"},{"in":"query","name":"lon","required":true,"schema":{"type":"number","format":"float"},"description":"Longitude coordinate"}],"responses":{"200":{"description":"Current weather conditions"},"400":{"description":"Invalid coordinates"},"401":{"description":"Unauthorized"}}}},"/api/v3/weather/forecast":{"get":{"tags":["Weather"],"summary":"Get 5-day weather forecast","description":"Retrieve a 5-day weather forecast for a GPS coordinate for proactive scheduling optimization.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"lat","required":true,"schema":{"type":"number","format":"float"},"description":"Latitude coordinate"},{"in":"query","name":"lon","required":true,"schema":{"type":"number","format":"float"},"description":"Longitude coordinate"}],"responses":{"200":{"description":"5-day weather forecast"},"400":{"description":"Invalid coordinates"},"401":{"description":"Unauthorized"}}}},"/api/v3/weather/impact/{serviceType}":{"get":{"tags":["Weather"],"summary":"Get current weather impact for a service type","description":"Assess how current weather conditions will affect the feasibility of a specific field service type.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"serviceType","required":true,"schema":{"type":"string"},"description":"Service type (e.g. exterior_painting, roofing, hvac)"}],"responses":{"200":{"description":"Weather impact assessment for the service type"},"401":{"description":"Unauthorized"}}}},"/api/v3/weather/forecast-impact/{serviceType}":{"get":{"tags":["Weather"],"summary":"Get weather impact forecast for next 5 days","description":"Get a day-by-day weather impact forecast for a service type over the next 5 days for scheduling optimization.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"serviceType","required":true,"schema":{"type":"string"},"description":"Service type (e.g. exterior_painting, roofing, hvac)"}],"responses":{"200":{"description":"5-day weather impact forecast"},"401":{"description":"Unauthorized"}}}},"/api/v3/weather/optimal-days/{serviceType}":{"get":{"tags":["Weather"],"summary":"Get optimal scheduling days based on weather","description":"Recommend the best upcoming days for scheduling a specific service type based on weather forecast.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"serviceType","required":true,"schema":{"type":"string"},"description":"Service type (e.g. exterior_painting, roofing, hvac)"}],"responses":{"200":{"description":"Optimal days recommendation"},"401":{"description":"Unauthorized"}}}},"/api/webhooks/stripe":{"post":{"tags":["Webhooks"],"summary":"Stripe billing webhook","description":"Receive billing events from Stripe including subscription changes, payment success/failure, and connect account events. Validates HMAC signature before processing.","security":[],"parameters":[{"in":"header","name":"stripe-signature","required":true,"schema":{"type":"string"},"description":"Stripe webhook signature for HMAC verification"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Stripe event object"}}}},"responses":{"200":{"description":"Event received and processed (or duplicate skipped)"},"400":{"description":"Malformed payload or processing failure"},"401":{"description":"Missing or invalid Stripe signature (canonical error envelope)"}}}},"/api/webhooks/twilio/sms":{"post":{"tags":["Webhooks"],"summary":"Twilio inbound SMS webhook","description":"Receive incoming SMS messages from Twilio and route to AI agent","security":[],"parameters":[{"in":"header","name":"x-twilio-signature","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"TwiML response"},"401":{"description":"Invalid signature"}}}},"/api/webhooks/twilio/status":{"post":{"tags":["Webhooks"],"summary":"Twilio delivery status webhook","description":"Receive SMS delivery status updates from Twilio","security":[],"parameters":[{"in":"header","name":"x-twilio-signature","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Status acknowledged"},"401":{"description":"Invalid signature"}}}},"/api/webhooks/twilio/compliance":{"post":{"tags":["Webhooks"],"summary":"Twilio A2P 10DLC compliance event ingestion","description":"Receives brand / campaign status-update events from Twilio\nTrustHub (or a Twilio Event Streams subscription forwarding\n`compliance.*` events) and applies them to the canonical\n`twilio_a2p_brands` / `twilio_a2p_campaigns` rows. The\nprimary status-sync path is the BullMQ poller — this webhook\nis the optional low-latency push channel for tenants that\nhave an Event Streams subscription configured.","security":[],"parameters":[{"in":"header","name":"x-twilio-signature","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Event acknowledged (applied or skipped — see body)"},"401":{"description":"Invalid Twilio signature"}}}},"/api/v3/work-orders/{id}/status":{"post":{"tags":["Work Orders"],"summary":"Change work order status","description":"Transition a work order to a new status (e.g. open → in_progress → completed). Requires work-orders:update permission.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Work order ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeWorkOrderStatus"}}}},"responses":{"200":{"description":"Status changed"},"400":{"description":"Invalid status transition"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"Work order not found"}}}},"/api/v3/work-orders/{id}/assign":{"post":{"tags":["Work Orders"],"summary":"Assign work order to technician","description":"Assign or reassign a work order to a specific technician. Requires work-orders:update permission.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Work order ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignWorkOrderTechnician"}}}},"responses":{"200":{"description":"Work order assigned"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"Work order not found"}}}},"/api/v3/work-orders/{id}/notes":{"post":{"tags":["Work Orders"],"summary":"Add work order note","description":"Add a progress note or update to a work order's activity log. Requires work-orders:update permission.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Work order ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddWorkOrderNote"}}}},"responses":{"201":{"description":"Note added"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permission"},"404":{"description":"Work order not found"}}},"get":{"tags":["Work Orders"],"summary":"Get work order notes","description":"Retrieve all progress notes and updates recorded against a work order.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Work order ID"}],"responses":{"200":{"description":"List of work order notes"},"401":{"description":"Unauthorized"},"404":{"description":"Work order not found"}}}},"/api/v3/work-orders/{id}/history":{"get":{"tags":["Work Orders"],"summary":"Get work order status history","description":"Retrieve the complete audit trail of status changes for a work order.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Work order ID"}],"responses":{"200":{"description":"Status change history"},"401":{"description":"Unauthorized"},"404":{"description":"Work order not found"}}}},"/api/v3/work-orders":{"get":{"tags":["Work Orders"],"summary":"List work orders","description":"Retrieve a paginated and filtered list of work orders for the tenant, supporting filters by status, technician, date range, and customer.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"status","schema":{"type":"string"},"description":"Filter by work order status"},{"in":"query","name":"technicianId","schema":{"type":"string","format":"uuid"},"description":"Filter by assigned technician"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Results per page"}],"responses":{"200":{"description":"Paginated list of work orders"},"401":{"description":"Unauthorized"}}},"post":{"tags":["Work Orders"],"summary":"Create work order","description":"Create a new work order for a customer job, assigning a technician, service type, and scheduling details.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkOrder"}}}},"responses":{"201":{"description":"Work order created successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions (requires work-orders:create)"}}}},"/api/v3/work-orders/{id}":{"get":{"tags":["Work Orders"],"summary":"Get work order by ID","description":"Retrieve full details of a specific work order including assigned technician, line items, notes, and status history.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Work order ID"}],"responses":{"200":{"description":"Work order details"},"401":{"description":"Unauthorized"},"404":{"description":"Work order not found"}}},"put":{"tags":["Work Orders"],"summary":"Update work order","description":"Replace all fields of an existing work order with the provided data, including status, assignment, and scheduling changes.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Work order ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkOrder"}}}},"responses":{"200":{"description":"Work order updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions (requires work-orders:update)"},"404":{"description":"Work order not found"}}},"patch":{"tags":["Work Orders"],"summary":"Partially update work order","description":"Apply partial updates to an existing work order; accepts any subset of work order fields for incremental modification.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Work order ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkOrder"}}}},"responses":{"200":{"description":"Work order partially updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions (requires work-orders:update)"},"404":{"description":"Work order not found"}}},"delete":{"tags":["Work Orders"],"summary":"Delete work order","description":"Permanently delete a work order and its associated data; this action cannot be undone.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Work order ID"}],"responses":{"200":{"description":"Work order deleted"},"401":{"description":"Unauthorized"},"403":{"description":"Insufficient permissions (requires work-orders:delete)"},"404":{"description":"Work order not found"}}}},"/api/v3/workflows/templates":{"get":{"tags":["Workflows"],"summary":"Get workflow templates","description":"Retrieve predefined workflow templates that can be used as starting points for automation setup (e.g. auto-invoice on job completion).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of workflow templates"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/connect":{"post":{"tags":["Yelp"],"summary":"Connect Yelp integration","description":"Establish the Yelp integration for the tenant by configuring the inbound webhook catch URL and activating lead syncing.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"catchHookUrl":{"type":"string","format":"uri","description":"Optional Zapier or webhook URL to receive Yelp events"}}}}}},"responses":{"200":{"description":"Yelp integration connected"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/disconnect":{"post":{"tags":["Yelp"],"summary":"Disconnect Yelp integration","description":"Remove the Yelp integration for the tenant, stopping lead syncing and disabling all Yelp-related functionality.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Yelp integration disconnected"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/rotate-secret":{"post":{"tags":["Yelp"],"summary":"Rotate Yelp inbound webhook secret","description":"Generate a fresh X-Zapier-Secret and overwrite the stored one. Returns the new secret once; the inbound Zap header must be updated to match or POSTs will 401.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Secret rotated (new secret returned once)"},"401":{"description":"Unauthorized"},"404":{"description":"No Yelp integration to rotate"}}}},"/api/v3/yelp":{"put":{"tags":["Yelp"],"summary":"Update Yelp integration settings","description":"Update Yelp integration configuration such as auto-respond behavior, conversation sync, and outbound webhook URL.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"autoRespondEnabled":{"type":"boolean"},"syncToConversations":{"type":"boolean"},"outboundCatchHookUrl":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Yelp settings updated"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/send-message":{"post":{"tags":["Yelp"],"summary":"Send message via Yelp","description":"Send an outbound message to a Yelp lead via the configured Zapier webhook, using the Yelp conversation ID to route the reply.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["leadId","conversationIdYelp","message"],"properties":{"leadId":{"type":"string","description":"Internal lead ID"},"conversationIdYelp":{"type":"string","description":"Yelp conversation thread ID"},"message":{"type":"string","maxLength":2000,"description":"Message text to send"}}}}}},"responses":{"200":{"description":"Message sent successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/status":{"get":{"tags":["Yelp"],"summary":"Get Yelp integration status","description":"Retrieve the current connection status of the Yelp integration, including whether it is active and the configured webhook URLs.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Yelp integration status"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/stats":{"get":{"tags":["Yelp"],"summary":"Get Yelp integration statistics","description":"Retrieve aggregate statistics for the Yelp integration, such as total leads received, conversion rates, and message volumes.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Yelp integration statistics"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/leads":{"get":{"tags":["Yelp"],"summary":"List Yelp leads","description":"Retrieve a paginated list of leads received from Yelp, optionally filtered by status or date range.","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"status","schema":{"type":"string"},"description":"Filter leads by status"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Results per page"}],"responses":{"200":{"description":"Paginated list of Yelp leads"},"401":{"description":"Unauthorized"}}}},"/api/v3/yelp/leads/{leadId}":{"get":{"tags":["Yelp"],"summary":"Get a Yelp lead by ID","description":"Retrieve full details of a specific Yelp lead including contact information, conversation history, and current status.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"leadId","required":true,"schema":{"type":"string"},"description":"Yelp lead ID"}],"responses":{"200":{"description":"Yelp lead details"},"401":{"description":"Unauthorized"},"404":{"description":"Lead not found"}}}}}}