diff --git a/api/constants.py b/api/constants.py index f44410c..ef949eb 100644 --- a/api/constants.py +++ b/api/constants.py @@ -142,3 +142,5 @@ FORCE_TURN_RELAY = os.getenv("FORCE_TURN_RELAY", "false").lower() == "true" # OSS Email/Password Auth OSS_JWT_SECRET = os.getenv("OSS_JWT_SECRET", "change-me-in-production") OSS_JWT_EXPIRY_HOURS = int(os.getenv("OSS_JWT_EXPIRY_HOURS", "720")) # 30 days + +TUNER_BASE_URL = os.getenv("TUNER_BASE_URL", "https://api.usetuner.ai") diff --git a/api/db/models.py b/api/db/models.py index f13fa7c..c733168 100644 --- a/api/db/models.py +++ b/api/db/models.py @@ -292,7 +292,9 @@ class IntegrationModel(Base): __tablename__ = "integrations" id = Column(Integer, primary_key=True, index=True) - integration_id = Column(String, nullable=False, index=True) # Nango Connection ID + integration_id = Column( + String, nullable=False, index=True + ) # External connection ID organization_id = Column(Integer, ForeignKey("organizations.id"), nullable=False) provider = Column(String, nullable=False) created_by = Column(Integer, ForeignKey("users.id")) @@ -555,8 +557,8 @@ class CampaignModel(Base): ) # Source configuration - source_type = Column(String, nullable=False, default="google-sheet") - source_id = Column(String, nullable=False) # Sheet URL + source_type = Column(String, nullable=False, default="csv") + source_id = Column(String, nullable=False) # CSV file key # State management state = Column( diff --git a/api/mcp_server/tools/node_types.py b/api/mcp_server/tools/node_types.py index 04e8c55..83d2369 100644 --- a/api/mcp_server/tools/node_types.py +++ b/api/mcp_server/tools/node_types.py @@ -40,15 +40,17 @@ async def list_node_types() -> dict: @traced_tool async def get_node_type(name: str) -> dict: - """Fetch the full schema for a node type, including every property's - type, default, conditional visibility rules, and LLM-readable - description, plus worked examples. + """Fetch the authoring schema for a node type: each property's name, + type, default, requiredness, enum options, validation bounds, and + LLM-readable description, plus worked examples and graph constraints. - Use the property `description` and the `examples` list to understand - semantics — types alone are not enough. + UI-only metadata (display labels, placeholders, conditional visibility + rules, renderer hints) is intentionally omitted — set only the fields + you need. Use the property `description`/`llm_hint` and the `examples` + list to understand semantics; types alone are not enough. """ await authenticate_mcp_request() spec = get_spec(name) if spec is None: raise HTTPException(status_code=404, detail=f"Unknown node type: {name!r}") - return spec.model_dump(mode="json") + return spec.to_mcp_dict() diff --git a/api/requirements.txt b/api/requirements.txt index 4801572..d96083e 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -18,4 +18,5 @@ bcrypt==5.0.0 email-validator==2.3.0 posthog==7.11.1 fastmcp==3.2.4 +tuner-pipecat-sdk==0.2.0 PyNaCl==1.6.2 diff --git a/api/routes/campaign.py b/api/routes/campaign.py index 407cef7..cb5f541 100644 --- a/api/routes/campaign.py +++ b/api/routes/campaign.py @@ -152,8 +152,8 @@ class CircuitBreakerConfigResponse(BaseModel): class CreateCampaignRequest(BaseModel): name: str = Field(..., min_length=1, max_length=255) workflow_id: int - source_type: str = Field(..., pattern="^(google-sheet|csv)$") - source_id: str # Google Sheet URL or CSV file key + source_type: str = Field(..., pattern="^csv$") + source_id: str # CSV file key # Optional during the legacy → multi-config migration window. Required in # a follow-up. When omitted, the dispatcher falls back to the org's # default config. @@ -929,8 +929,6 @@ async def get_campaign_source_download_url( user: UserModel = Depends(get_user), ) -> CampaignSourceDownloadResponse: """Get presigned download URL for campaign CSV source file - - Only works for CSV source type. For Google Sheets, use the source_id directly. Validates that the campaign belongs to the user's organization for security. """ # Verify campaign exists and belongs to organization diff --git a/api/routes/integration.py b/api/routes/integration.py deleted file mode 100644 index 09498ff..0000000 --- a/api/routes/integration.py +++ /dev/null @@ -1,266 +0,0 @@ -""" -Route for 3rd party integrations. Currently being backed by nango. -""" - -from dataclasses import dataclass -from typing import Any, Dict, List, Optional, TypedDict - -from fastapi import APIRouter, Depends, HTTPException, Request -from loguru import logger -from pydantic import BaseModel - -from api.db import db_client -from api.db.models import UserModel -from api.services.auth.depends import get_user -from api.services.integrations.nango import nango_service - -router = APIRouter(prefix="/integration") - - -@dataclass -class IntegrationResponse: - id: int - integration_id: str - organization_id: int - created_by: Optional[int] - provider: str - is_active: bool - created_at: str - action: str - provider_data: dict - - -class SessionResponse(TypedDict): - session_token: str - expires_at: str - - -class WebhookResponse(TypedDict): - status: str - message: str - - -class UpdateIntegrationRequest(BaseModel): - selected_files: List[Dict[str, Any]] - - -class AccessTokenResponse(BaseModel): - access_token: Optional[str] - refresh_token: Optional[str] - expires_at: Optional[str] - connection_id: str - - -def build_integration_response(integration) -> IntegrationResponse: - """Build a standardized integration response with provider-specific data.""" - provider_data = {} - - if integration.provider == "google-sheet": - # For Google Sheets, include selected_files - provider_data["selected_files"] = integration.connection_details.get( - "selected_files", [] - ) - elif integration.provider == "slack": - # For Slack, include channel information - channel = integration.connection_details.get("connection_config", {}).get( - "incoming_webhook.channel" - ) - if channel: - provider_data["channel"] = channel - - return IntegrationResponse( - id=integration.id, - integration_id=integration.integration_id, - organization_id=integration.organization_id, - created_by=integration.created_by, - provider=integration.provider, - is_active=integration.is_active, - created_at=integration.created_at.isoformat(), - action=integration.action, - provider_data=provider_data, - ) - - -@router.get("/") -async def get_integrations( - user: UserModel = Depends(get_user), -) -> list[IntegrationResponse]: - """ - Get all integrations for the user's selected organization. - - Returns: - List of integrations associated with the user's selected organization - """ - if not user.selected_organization_id: - raise HTTPException( - status_code=400, detail="No organization selected for the user" - ) - - integrations = await db_client.get_integrations_by_organization_id( - user.selected_organization_id - ) - - return [build_integration_response(integration) for integration in integrations] - - -@router.post("/session") -async def create_session( - user: UserModel = Depends(get_user), -) -> SessionResponse: - """ - Create a Nango session for the user's selected organization. - - Returns: - Session token and ID for the created session - """ - if not user.selected_organization_id: - raise HTTPException( - status_code=400, detail="No organization selected for the user" - ) - - try: - session_data = await nango_service.create_session( - user_id=str(user.id), organization_id=user.selected_organization_id - ) - - return { - "session_token": session_data["data"]["token"], - "expires_at": session_data["data"]["expires_at"], - } - except ValueError as e: - raise HTTPException(status_code=500, detail=str(e)) - except Exception as e: - raise HTTPException( - status_code=500, detail=f"Failed to create session: {str(e)}" - ) - - -@router.put("/{integration_id}") -async def update_integration( - integration_id: int, - request: UpdateIntegrationRequest, - user: UserModel = Depends(get_user), -) -> IntegrationResponse: - """ - Update an integration's selected files (for Google Sheets). - - Args: - integration_id: The ID of the integration to update - request: The update request containing selected files - user: The authenticated user - - Returns: - Updated integration details - """ - if not user.selected_organization_id: - raise HTTPException( - status_code=400, detail="No organization selected for the user" - ) - - # Get the integration first to verify ownership - integrations = await db_client.get_integrations_by_organization_id( - user.selected_organization_id - ) - - integration = next((i for i in integrations if i.id == integration_id), None) - if not integration: - raise HTTPException(status_code=404, detail="Integration not found") - - # Only allow updating selected_files for google-sheet provider - if integration.provider != "google-sheet": - raise HTTPException( - status_code=400, - detail="This endpoint only supports updating Google Sheet integrations", - ) - - # Update the connection_details with the new selected_files - updated_connection_details = integration.connection_details.copy() - updated_connection_details["selected_files"] = request.selected_files - - # Update the integration - updated_integration = await db_client.update_integration_connection_details( - integration_id=integration_id, connection_details=updated_connection_details - ) - - if not updated_integration: - raise HTTPException(status_code=500, detail="Failed to update integration") - - return build_integration_response(updated_integration) - - -@router.get("/{integration_id}/access-token") -async def get_integration_access_token( - integration_id: int, - user: UserModel = Depends(get_user), -) -> AccessTokenResponse: - """ - Get the latest access token for an integration from Nango. - - Args: - integration_id: The ID of the integration - user: The authenticated user - - Returns: - Dict containing access token and expiration info - """ - if not user.selected_organization_id: - raise HTTPException( - status_code=400, detail="No organization selected for the user" - ) - - # Get the integration to verify ownership and get connection details - integrations = await db_client.get_integrations_by_organization_id( - user.selected_organization_id - ) - - integration = next((i for i in integrations if i.id == integration_id), None) - if not integration: - raise HTTPException(status_code=404, detail="Integration not found") - - try: - # Fetch the latest access token from Nango - token_data = await nango_service.get_access_token( - connection_id=integration.integration_id, - provider_config_key=integration.provider, - ) - - # Extract relevant fields - return AccessTokenResponse( - access_token=token_data.get("credentials", {}).get("access_token"), - refresh_token=token_data.get("credentials", {}).get("refresh_token"), - expires_at=token_data.get("credentials", {}).get("expires_at"), - connection_id=integration.integration_id, - ) - - except Exception as e: - logger.error(f"Failed to get access token: {str(e)}") - raise HTTPException( - status_code=500, detail=f"Failed to fetch access token: {str(e)}" - ) - - -@router.post("/webhook", include_in_schema=False) -async def handle_nango_webhook( - request: Request, -) -> WebhookResponse: - """ - Handle Nango integration webhook requests. - - Processes webhook events from Nango when integrations are created/updated - and stores the integration details in the database. - - Args: - request: The raw FastAPI request object - - Returns: - WebhookResponse with status and message - """ - raw_body = await request.body() - - # Get signature from headers (you may need to adjust the header name) - signature = request.headers.get("X-Nango-Signature") - - # Use the nango service to process the webhook - result = await nango_service.process_webhook(raw_body, signature) - - return result diff --git a/api/routes/main.py b/api/routes/main.py index 6bcd3dc..e984bdd 100644 --- a/api/routes/main.py +++ b/api/routes/main.py @@ -6,7 +6,6 @@ from api.routes.agent_stream import router as agent_stream_router from api.routes.auth import router as auth_router from api.routes.campaign import router as campaign_router from api.routes.credentials import router as credentials_router -from api.routes.integration import router as integration_router from api.routes.knowledge_base import router as knowledge_base_router from api.routes.node_types import router as node_types_router from api.routes.organization import router as organization_router @@ -26,6 +25,7 @@ from api.routes.webrtc_signaling import router as webrtc_signaling_router from api.routes.workflow import router as workflow_router from api.routes.workflow_embed import router as workflow_embed_router from api.routes.workflow_recording import router as workflow_recording_router +from api.services.integrations import all_routers router = APIRouter( tags=["main"], @@ -39,7 +39,6 @@ router.include_router(user_router) router.include_router(campaign_router) router.include_router(credentials_router) router.include_router(tool_router) -router.include_router(integration_router) router.include_router(organization_router) router.include_router(s3_router) router.include_router(service_keys_router) @@ -57,6 +56,9 @@ router.include_router(auth_router) router.include_router(node_types_router) router.include_router(agent_stream_router) +for _integration_router in all_routers(): + router.include_router(_integration_router) + class HealthResponse(BaseModel): status: str diff --git a/api/routes/public_download.py b/api/routes/public_download.py index 38793d9..c84cc24 100644 --- a/api/routes/public_download.py +++ b/api/routes/public_download.py @@ -1,8 +1,9 @@ """Public download endpoints for workflow recordings and transcripts. These endpoints provide secure, token-based public access to workflow artifacts -without requiring authentication. Tokens are generated on-demand when webhooks -are executed and included in the webhook payload. +without requiring authentication. Tokens are generated on-demand during +post-call processing for runs that execute integrations, QA, or campaign +reporting. """ from typing import Literal diff --git a/api/services/campaign/source_sync.py b/api/services/campaign/source_sync.py index 5393b65..1f0ee67 100644 --- a/api/services/campaign/source_sync.py +++ b/api/services/campaign/source_sync.py @@ -183,9 +183,7 @@ class CampaignSourceSyncService(ABC): async def get_source_credentials( self, organization_id: int, source_type: str ) -> Dict[str, Any]: - """Gets OAuth tokens or API credentials via Nango""" - # This would be implemented to work with Nango service - # For now, returning placeholder + """Gets source credentials when a sync service requires them.""" logger.info( f"Getting credentials for org {organization_id}, source {source_type}" ) diff --git a/api/services/campaign/source_sync_factory.py b/api/services/campaign/source_sync_factory.py index 2e05f1b..8725f4f 100644 --- a/api/services/campaign/source_sync_factory.py +++ b/api/services/campaign/source_sync_factory.py @@ -1,15 +1,12 @@ from api.services.campaign.source_sync import CampaignSourceSyncService from api.services.campaign.sources.csv import CSVSyncService -from api.services.campaign.sources.google_sheets import GoogleSheetsSyncService def get_sync_service(source_type: str) -> CampaignSourceSyncService: """Returns appropriate sync service based on source type""" services = { - "google-sheet": GoogleSheetsSyncService, "csv": CSVSyncService, - # Add more as needed: "hubspot": HubSpotSyncService, } service_class = services.get(source_type) diff --git a/api/services/campaign/sources/__init__.py b/api/services/campaign/sources/__init__.py index 3c283ee..1a4dc5d 100644 --- a/api/services/campaign/sources/__init__.py +++ b/api/services/campaign/sources/__init__.py @@ -1,5 +1,3 @@ """Campaign source sync services""" -from .google_sheets import GoogleSheetsSyncService - -__all__ = ["GoogleSheetsSyncService"] +__all__: list[str] = [] diff --git a/api/services/campaign/sources/google_sheets.py b/api/services/campaign/sources/google_sheets.py deleted file mode 100644 index ea473f5..0000000 --- a/api/services/campaign/sources/google_sheets.py +++ /dev/null @@ -1,224 +0,0 @@ -import re -from typing import Any, Dict, List, Optional - -import httpx -from loguru import logger - -from api.db import db_client -from api.services.campaign.source_sync import ( - CampaignSourceSyncService, - ValidationError, - ValidationResult, -) -from api.services.integrations.nango import NangoService - - -class GoogleSheetsSyncService(CampaignSourceSyncService): - """Implementation for Google Sheets synchronization""" - - def __init__(self): - self.nango_service = NangoService() - self.sheets_api_base = "https://sheets.googleapis.com/v4/spreadsheets" - - async def _get_access_token(self, organization_id: int) -> str: - """Get OAuth access token for Google Sheets via Nango.""" - integrations = await db_client.get_integrations_by_organization_id( - organization_id - ) - integration = None - for intg in integrations: - if intg.provider == "google-sheet" and intg.is_active: - integration = intg - break - - if not integration: - raise ValueError("Google Sheets integration not found or inactive") - - token_data = await self.nango_service.get_access_token( - connection_id=integration.integration_id, provider_config_key="google-sheet" - ) - return token_data["credentials"]["access_token"] - - async def _fetch_all_sheet_data( - self, sheet_url: str, organization_id: int - ) -> List[List[str]]: - """Fetch all data from a Google Sheet. Returns all rows including header.""" - access_token = await self._get_access_token(organization_id) - sheet_id = self._extract_sheet_id(sheet_url) - - metadata = await self._get_sheet_metadata(sheet_id, access_token) - if not metadata.get("sheets"): - raise ValueError("No sheets found in the spreadsheet") - - sheet_name = metadata["sheets"][0]["properties"]["title"] - - return await self._fetch_sheet_data(sheet_id, f"{sheet_name}!A:Z", access_token) - - async def validate_source( - self, source_id: str, organization_id: Optional[int] = None - ) -> ValidationResult: - """Validate a Google Sheet source for campaign creation.""" - if organization_id is None: - return ValidationResult( - is_valid=False, - error=ValidationError( - message="Organization ID is required for Google Sheets validation" - ), - ) - - # Validate URL format first - pattern = r"/spreadsheets/d/([a-zA-Z0-9-_]+)" - if not re.search(pattern, source_id): - return ValidationResult( - is_valid=False, - error=ValidationError( - message=f"Invalid Google Sheets URL: {source_id}" - ), - ) - - try: - rows = await self._fetch_all_sheet_data(source_id, organization_id) - except ValueError as e: - return ValidationResult( - is_valid=False, - error=ValidationError(message=str(e)), - ) - except httpx.HTTPStatusError as e: - logger.error(f"HTTP error fetching Google Sheet: {e.response.status_code}") - return ValidationResult( - is_valid=False, - error=ValidationError( - message=f"Failed to fetch Google Sheet data: {e.response.status_code}" - ), - ) - except Exception as e: - logger.error(f"Error fetching Google Sheet: {e}") - return ValidationResult( - is_valid=False, - error=ValidationError(message="Failed to fetch Google Sheet data"), - ) - - if not rows or len(rows) < 2: - return ValidationResult( - is_valid=False, - error=ValidationError( - message="Google Sheet must have a header row and at least one data row" - ), - ) - - headers = rows[0] - data_rows = rows[1:] - - return self.validate_source_data(headers, data_rows) - - async def sync_source_data(self, campaign_id: int) -> int: - """ - Fetches data from Google Sheets and creates queued_runs - """ - # Get campaign - campaign = await db_client.get_campaign_by_id(campaign_id) - if not campaign: - raise ValueError(f"Campaign {campaign_id} not found") - - rows = await self._fetch_all_sheet_data( - campaign.source_id, campaign.organization_id - ) - - if not rows or len(rows) < 2: - logger.warning(f"No data found in sheet for campaign {campaign_id}") - return 0 - - headers = self.normalize_headers(rows[0]) - data_rows = rows[1:] - - sheet_id = self._extract_sheet_id(campaign.source_id) - - queued_runs = [] - for idx, row_values in enumerate(data_rows, 1): - # Pad row to match headers length - padded_row = row_values + [""] * (len(headers) - len(row_values)) - - # Create context variables dict - context_vars = dict(zip(headers, padded_row)) - - # Skip if no phone number - if not context_vars.get("phone_number"): - logger.debug(f"Skipping row {idx}: no phone_number") - continue - - # Generate unique source UUID - source_uuid = f"sheet_{sheet_id}_row_{idx}" - - queued_runs.append( - { - "campaign_id": campaign_id, - "source_uuid": source_uuid, - "context_variables": context_vars, - "state": "queued", - } - ) - - # Bulk insert - if queued_runs: - await db_client.bulk_create_queued_runs(queued_runs) - logger.info( - f"Created {len(queued_runs)} queued runs for campaign {campaign_id}" - ) - - # Update campaign total_rows - await db_client.update_campaign( - campaign_id=campaign_id, - total_rows=len(queued_runs), - source_sync_status="completed", - ) - - return len(queued_runs) - - async def _fetch_sheet_data( - self, sheet_id: str, range: str, access_token: str - ) -> List[List[str]]: - """Fetch data from Google Sheets API""" - url = f"{self.sheets_api_base}/{sheet_id}/values/{range}" - headers = {"Authorization": f"Bearer {access_token}"} - - async with httpx.AsyncClient() as client: - response = await client.get(url, headers=headers) - response.raise_for_status() - - data = response.json() - return data.get("values", []) - - async def _get_sheet_metadata( - self, sheet_id: str, access_token: str - ) -> Dict[str, Any]: - """Get sheet metadata including sheet names""" - url = f"{self.sheets_api_base}/{sheet_id}" - headers = {"Authorization": f"Bearer {access_token}"} - - logger.debug(f"Fetching sheet metadata from URL: {url}") - logger.debug(f"Using sheet_id: {sheet_id}") - - async with httpx.AsyncClient() as client: - try: - response = await client.get(url, headers=headers) - response.raise_for_status() - return response.json() - except httpx.HTTPStatusError as e: - logger.error(f"HTTP error {e.response.status_code} for URL: {url}") - logger.error(f"Response body: {e.response.text}") - raise - except Exception as e: - logger.error(f"Error fetching sheet metadata: {e}") - raise - - def _extract_sheet_id(self, sheet_url: str) -> str: - """ - Extract sheet ID from various Google Sheets URL formats: - - https://docs.google.com/spreadsheets/d/{id}/edit - - https://docs.google.com/spreadsheets/d/{id}/edit#gid=0 - """ - pattern = r"/spreadsheets/d/([a-zA-Z0-9-_]+)" - match = re.search(pattern, sheet_url) - if match: - return match.group(1) - raise ValueError(f"Invalid Google Sheets URL: {sheet_url}") diff --git a/api/services/configuration/masking.py b/api/services/configuration/masking.py index 7676bfb..3aee31c 100644 --- a/api/services/configuration/masking.py +++ b/api/services/configuration/masking.py @@ -13,6 +13,7 @@ from typing import Any, Dict, Optional from api.schemas.user_configuration import UserConfiguration from api.services.configuration.registry import ServiceConfig +from api.services.integrations import get_node_secret_fields VISIBLE_CHARS = 4 # number of trailing characters to reveal MASK_CHAR = "*" @@ -129,14 +130,22 @@ def mask_user_config(config: UserConfiguration) -> Dict[str, Any]: # --------------------------------------------------------------------------- -# Workflow definition helpers – mask / merge QA-node API keys +# Workflow definition helpers – mask / merge node API keys # --------------------------------------------------------------------------- -_QA_API_KEY_FIELD = "qa_api_key" +_NODE_SECRET_FIELDS: dict[str, tuple[str, ...]] = { + "qa": ("qa_api_key",), +} + + +def _secret_fields_for_node_type(node_type: str | None) -> tuple[str, ...]: + if not node_type: + return () + return _NODE_SECRET_FIELDS.get(node_type, ()) or get_node_secret_fields(node_type) def mask_workflow_definition(workflow_definition: Optional[Dict]) -> Optional[Dict]: - """Return a *shallow copy* of *workflow_definition* with QA-node API keys masked.""" + """Return a copy of *workflow_definition* with node secret fields masked.""" if not workflow_definition: return workflow_definition @@ -144,47 +153,46 @@ def mask_workflow_definition(workflow_definition: Optional[Dict]) -> Optional[Di masked = copy.deepcopy(workflow_definition) for node in masked.get("nodes", []): - if node.get("type") != "qa": + secret_fields = _secret_fields_for_node_type(node.get("type")) + if not secret_fields: continue data = node.get("data", {}) - raw_key = data.get(_QA_API_KEY_FIELD) - if raw_key: - data[_QA_API_KEY_FIELD] = mask_key(raw_key) + for field in secret_fields: + raw_key = data.get(field) + if raw_key: + data[field] = mask_key(raw_key) return masked def merge_workflow_api_keys( incoming_definition: Optional[Dict], existing_definition: Optional[Dict] ) -> Optional[Dict]: - """Preserve real QA-node API keys when the incoming value is a masked placeholder. - - For each QA node in *incoming_definition*, if its ``qa_api_key`` equals - the masked form of the corresponding node in *existing_definition*, the - real key is restored so it is never lost. - """ + """Preserve real node secret fields when the incoming value is masked.""" if not incoming_definition or not existing_definition: return incoming_definition - # Build lookup: node-id → data for existing QA nodes - existing_qa: Dict[str, Dict] = {} + existing_nodes: Dict[str, Dict] = {} for node in existing_definition.get("nodes", []): - if node.get("type") == "qa": - existing_qa[node["id"]] = node.get("data", {}) + if _secret_fields_for_node_type(node.get("type")): + existing_nodes[node["id"]] = node.get("data", {}) for node in incoming_definition.get("nodes", []): - if node.get("type") != "qa": + secret_fields = _secret_fields_for_node_type(node.get("type")) + if not secret_fields: continue data = node.get("data", {}) - incoming_key = data.get(_QA_API_KEY_FIELD) - if not incoming_key: - continue - old_data = existing_qa.get(node["id"]) + old_data = existing_nodes.get(node["id"]) if not old_data: continue - old_key = old_data.get(_QA_API_KEY_FIELD, "") - if old_key and is_mask_of(incoming_key, old_key): - data[_QA_API_KEY_FIELD] = old_key + for field in secret_fields: + incoming_key = data.get(field) + if not incoming_key: + continue + + old_key = old_data.get(field, "") + if old_key and is_mask_of(incoming_key, old_key): + data[field] = old_key return incoming_definition diff --git a/api/services/integrations/AGENTS.md b/api/services/integrations/AGENTS.md new file mode 100644 index 0000000..44ba9b9 --- /dev/null +++ b/api/services/integrations/AGENTS.md @@ -0,0 +1,239 @@ +# Integrations - Plugin Contract + +`api/services/integrations/` is the extension seam for third-party integrations. +New integrations should be self-contained here. Do not bleed integration-specific +logic into `workflow/dto.py`, `workflow/node_specs/`, `run_pipeline.py`, +`event_handlers.py`, or `run_integrations.py` unless you are changing the generic +framework itself. + +## Golden Path + +Create a package: + +```text +api/services/integrations// +├── __init__.py +├── node.py +├── runtime.py # optional +├── completion.py # optional +├── routes.py # optional +└── client.py # optional +``` + +The package self-registers on import via `register_package(...)`. Discovery is +automatic: `api/services/integrations/loader.py` imports every submodule under +`api.services.integrations` except the reserved internal names `base`, `loader`, +and `registry`. + +## Registration Pattern + +`__init__.py` should register one `IntegrationPackageSpec`, following the +existing integration packages in this directory. + +Use: + +```python +PACKAGE = register_package( + IntegrationPackageSpec( + name="", + nodes=(NODE,), + create_runtime_sessions=create_runtime_sessions, # optional + run_completion=run_completion, # optional + routers=(router,), # optional + ) +) +``` + +The package name is the registry key. The node `type_name` is the workflow node +type string and must stay stable once exposed. + +## Node Model + Spec + +For integration nodes, the Pydantic model is the source of truth. The serialized +`NodeSpec` is derived from it. + +Refer to an existing integration node for the overall structure: + +- Define one Pydantic model per node, inheriting + `api/services/workflow/node_data.py:BaseNodeData`. +- Annotate it with `@node_spec(...)`. +- Define fields with `spec_field(...)`. +- Generate the external spec with `SPEC = build_spec(ModelClass)`. +- Register the node with `IntegrationNodeRegistration(...)`. + +Important rules: + +- Put runtime validation in the model, not in the generated spec. + Example: conditional requiredness belongs in `@model_validator(mode="after")`. +- Keep `@node_spec(name=...)` and `IntegrationNodeRegistration.type_name` + identical. They are the same workflow node type string. +- Put wire constraints in the field itself where possible. + Example: `gt=0`, `min_length=1`, `pattern=...`. +- Put UI/export-only differences in `field_overrides`. + Use this for `display_name`, `description`, `required`, `spec_default`, + `display_options`, or property ordering. +- Use `spec_exclude=True` for internal fields that must exist in persisted data + but must not show up in `/api/v1/node-types`. +- Set `property_order=(...)` in `@node_spec(...)` when the editor field order + must remain stable. + +Typical workflow graph constraints for configuration-only integration nodes: + +```python +GraphConstraints(min_incoming=0, max_incoming=0, min_outgoing=0, max_outgoing=0) +``` + +These constraints control how the node can be connected in the workflow graph. +Use them for configuration nodes that are not conversational graph steps. + +## Secret Fields + +If the node stores secrets, register them in +`IntegrationNodeRegistration.sensitive_fields`. + +That is enough for generic masking / masked round-trip preservation via +`api/services/configuration/masking.py`. Do not add new integration-specific +masking branches unless you are changing the shared masking framework. + +## No Central DTO Edits + +Do not add integration node classes to `api/services/workflow/dto.py`. + +Integration nodes are resolved dynamically through: + +- `get_node_data_model()` in `workflow/dto.py` +- `get_node_spec()` / `all_node_specs()` in `services/integrations/registry.py` + +`RFNodeDTO` validates integration nodes by `type` through the registry. That is +the intended extension path. + +## Live Call Path + +If the integration needs live call data, implement `create_runtime_sessions(...)` +in `runtime.py` and return `IntegrationRuntimeSession` objects. + +The generic wiring is already in `api/services/pipecat/run_pipeline.py`: + +- `create_runtime_sessions(IntegrationRuntimeContext(...))` is called before the + pipeline task starts. +- Each returned session gets `session.attach(task)` called. + +Use this only for lightweight live collection: + +- attach task observers +- read context messages +- capture timing / turn / tool events +- build an in-memory snapshot + +Do not do outbound network I/O in the live path unless there is a very strong +reason. Prefer the standard pattern: collect live, deliver after the call. + +`IntegrationRuntimeContext` gives you: + +- `workflow_run_id` +- `workflow_run` +- `workflow_graph` +- `run_definition` +- `user_config` +- `is_realtime` +- `context_messages_provider` + +Typical runtime pattern: + +- scan `context.workflow_graph.nodes.values()` for enabled nodes of your type +- if none are enabled, return `[]` +- build one collector/session per workflow run, not per node, unless the + integration truly needs multiple independent collectors + +## Call-Finish Snapshot Path + +`api/services/pipecat/event_handlers.py` finalizes runtime sessions before the +engine is cleaned up. + +The generic flow: + +1. `on_pipeline_finished` builds `gathered_context` +2. each runtime session gets `await session.on_call_finished(...)` +3. returned dicts are merged into `integration_logs` +4. those logs are persisted into `workflow_run.logs` + +Use `on_call_finished(...)` to emit a compact, serializable snapshot that the +post-call completion handler can consume later. Return `None` if there is nothing +to persist. + +This is the handoff between the live call path and the post-call task path. + +## Post-Call Completion Path + +If the integration needs durable artifacts, public URLs, retries, or external +delivery, implement `run_completion(nodes, context)` in `completion.py`. + +The generic orchestration is already in `api/tasks/run_integrations.py`: + +1. load the pinned workflow definition from the workflow run +2. create a public token if post-call work exists +3. run QA nodes first +4. run registered integration completion handlers +5. run webhook nodes last + +Your handler receives: + +- `nodes`: raw workflow node dicts for your node types only +- `IntegrationCompletionContext`: + - `workflow_run_id` + - `workflow_run` + - `workflow_definition` + - `definition_id` + - `organization_id` + - `public_token` + +Expected completion handler pattern: + +- validate each node with `YourNodeData.model_validate(node.get("data", {}))` +- skip disabled nodes +- read any runtime snapshot from `context.workflow_run.logs` +- build durable URLs using `public_token` when appropriate +- perform external delivery +- return a result dict keyed per node, usually with `node_id` embedded + +Returned data is merged into `workflow_run.annotations`. + +Do not assume completion runs inside the live pipeline process. Treat it as a +separate post-call worker step. + +## Optional Routes + +If an integration exposes HTTP routes, put them in `routes.py` and include the +router in `IntegrationPackageSpec.routers`. + +Routers are mounted automatically by `api/routes/main.py` through `all_routers()`. +Do not edit `routes/main.py` for per-integration route wiring. + +## Import Discipline + +Keep package import side effects light. + +The integration loader runs during: + +- node-type/spec enumeration +- tests +- route startup +- registry access + +So avoid top-level imports that require environment variables, network access, +or heavyweight initialization when possible. Prefer lazy imports inside +`run_completion()` / `create_runtime_sessions()` if the dependency is optional or +environment-sensitive. + +## Testing Expectations + +At minimum, new integrations should add coverage for: + +- node model validation +- generated spec/example validity +- secret masking + masked round-trip preservation if secrets exist +- runtime snapshot creation if live collectors exist +- completion handler happy path and disabled-node skip path + +If you change shared integration machinery, test the framework in the generic +code path, not only the concrete integration. diff --git a/api/services/integrations/__init__.py b/api/services/integrations/__init__.py index e69de29..f1483d6 100644 --- a/api/services/integrations/__init__.py +++ b/api/services/integrations/__init__.py @@ -0,0 +1,39 @@ +from api.services.integrations.base import ( + IntegrationCompletionContext, + IntegrationNodeRegistration, + IntegrationPackageSpec, + IntegrationRuntimeContext, + IntegrationRuntimeSession, +) +from api.services.integrations.registry import ( + all_node_specs, + all_packages, + all_routers, + create_runtime_sessions, + get_node_data_model, + get_node_registration, + get_node_secret_fields, + get_node_spec, + has_completion_handlers, + register_package, + run_completion_handlers, +) + +__all__ = [ + "IntegrationCompletionContext", + "IntegrationNodeRegistration", + "IntegrationPackageSpec", + "IntegrationRuntimeContext", + "IntegrationRuntimeSession", + "all_node_specs", + "all_packages", + "all_routers", + "create_runtime_sessions", + "get_node_data_model", + "get_node_registration", + "get_node_secret_fields", + "get_node_spec", + "has_completion_handlers", + "register_package", + "run_completion_handlers", +] diff --git a/api/services/integrations/base.py b/api/services/integrations/base.py new file mode 100644 index 0000000..b591474 --- /dev/null +++ b/api/services/integrations/base.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Awaitable, Callable, Protocol + +from fastapi import APIRouter + +from api.services.workflow.node_data import BaseNodeData +from api.services.workflow.node_specs._base import NodeSpec + + +class IntegrationRuntimeSession(Protocol): + name: str + + def attach(self, task: Any) -> None: ... + + async def on_call_finished( + self, + *, + gathered_context: dict[str, Any], + ) -> dict[str, Any] | None: ... + + +@dataclass(frozen=True) +class IntegrationRuntimeContext: + workflow_run_id: int + workflow_run: Any + workflow_graph: Any + run_definition: Any + user_config: Any + is_realtime: bool + context_messages_provider: Callable[[], list[dict[str, Any]]] + + +@dataclass(frozen=True) +class IntegrationCompletionContext: + workflow_run_id: int + workflow_run: Any + workflow_definition: dict[str, Any] + definition_id: int | None + organization_id: int + public_token: str | None + + +RuntimeFactory = Callable[ + [IntegrationRuntimeContext], + list[IntegrationRuntimeSession], +] +CompletionHandler = Callable[ + [list[dict[str, Any]], IntegrationCompletionContext], + Awaitable[dict[str, Any]], +] + + +@dataclass(frozen=True) +class IntegrationNodeRegistration: + type_name: str + data_model: type[BaseNodeData] + node_spec: NodeSpec + sensitive_fields: tuple[str, ...] = () + + +@dataclass(frozen=True) +class IntegrationPackageSpec: + name: str + nodes: tuple[IntegrationNodeRegistration, ...] = () + routers: tuple[APIRouter, ...] = () + create_runtime_sessions: RuntimeFactory | None = None + run_completion: CompletionHandler | None = None diff --git a/api/services/integrations/loader.py b/api/services/integrations/loader.py new file mode 100644 index 0000000..c5dca70 --- /dev/null +++ b/api/services/integrations/loader.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import importlib +import pkgutil + +_INTERNAL_MODULES = {"base", "loader", "registry"} +_loaded = False + + +def ensure_integrations_loaded() -> None: + global _loaded + if _loaded: + return + + package = importlib.import_module("api.services.integrations") + for module_info in pkgutil.iter_modules(package.__path__): + if module_info.name in _INTERNAL_MODULES: + continue + importlib.import_module(f"{package.__name__}.{module_info.name}") + + _loaded = True diff --git a/api/services/integrations/nango.py b/api/services/integrations/nango.py deleted file mode 100644 index a9c6523..0000000 --- a/api/services/integrations/nango.py +++ /dev/null @@ -1,253 +0,0 @@ -import hashlib -import json -import os -from typing import Any, Dict - -import httpx -from fastapi import HTTPException -from loguru import logger -from pydantic import BaseModel - -from api.db import db_client - -NANGO_ALLOWED_INTEGRATIONS = [ - i.strip() for i in os.environ.get("NANGO_ALLOWED_INTEGRATIONS", "slack").split(",") -] - - -class NangoWebhookRequest(BaseModel): - type: str - connectionId: str - providerConfigKey: str - authMode: str - provider: str - environment: str - operation: str - endUser: dict # Contains endUserId and organizationId - success: bool - - -class NangoService: - def __init__(self): - self.base_url = "https://api.nango.dev" - self.secret_key = os.getenv("NANGO_API_KEY") - - def _verify_webhook_signature( - self, request_body: str, signature: str = None - ) -> bool: - """ - Verify the webhook signature using SHA256 hash. - - Args: - request_body: The raw request body as string - signature: The signature from request headers (optional for now) - - Returns: - True if signature is valid - """ - expected_signature = self.secret_key + request_body - expected_hash = hashlib.sha256(expected_signature.encode("utf-8")).hexdigest() - return expected_hash == signature - - async def create_session( - self, user_id: str, organization_id: int - ) -> Dict[str, Any]: - """ - Create a Nango session for the given user and organization. - - Args: - user_id: The end user ID - organization_id: The organization ID - - Returns: - Response from Nango API - """ - if not self.secret_key: - raise ValueError("NANGO_SECRET_KEY environment variable is not set") - - headers = { - "Authorization": f"Bearer {self.secret_key}", - "Content-Type": "application/json", - } - - payload = { - "end_user": {"id": user_id}, - "organization": {"id": str(organization_id)}, - "allowed_integrations": NANGO_ALLOWED_INTEGRATIONS, - } - - async with httpx.AsyncClient() as client: - response = await client.post( - f"{self.base_url}/connect/sessions", headers=headers, json=payload - ) - - if response.status_code != 201: - raise httpx.HTTPStatusError( - f"Nango API error: {response.status_code}", - request=response.request, - response=response, - ) - - return response.json() - - async def process_webhook( - self, raw_body: bytes, signature: str = None - ) -> Dict[str, str]: - """ - Process incoming Nango webhook request. - - Args: - raw_body: The raw request body as bytes - signature: Optional signature from request headers - - Returns: - Dict with status and message - """ - # Decode and parse the request body - try: - body_text = raw_body.decode("utf-8") - webhook_json = json.loads(body_text) if body_text else {} - logger.debug(f"received webhook from nango: {webhook_json}") - except json.JSONDecodeError as e: - logger.error(f"JSON decode error: {e} body_text: {body_text}") - raise HTTPException(status_code=400, detail=f"Invalid JSON: {str(e)}") - - # Verify webhook signature - if not self._verify_webhook_signature(body_text, signature): - raise HTTPException(status_code=401, detail="Invalid webhook signature") - - # Parse webhook data - try: - webhook_data = NangoWebhookRequest(**webhook_json) - except Exception as e: - logger.error(f"Failed to parse webhook data: {e}") - raise HTTPException( - status_code=400, detail=f"Invalid webhook format: {str(e)}" - ) - - # Extract user and organization IDs from the webhook payload - end_user = webhook_data.endUser - if ( - not end_user - or "endUserId" not in end_user - or "organizationId" not in end_user - ): - raise HTTPException( - status_code=400, detail="Missing endUser information in webhook payload" - ) - - user_id = int(end_user["endUserId"]) - organization_id = int(end_user["organizationId"]) - - # Use the connectionId as the integration_id since it's unique per integration - integration_id = webhook_data.connectionId - - # Initialize connection_details - connection_details = {} - - # Fetch connection details if type is auth and provider is slack - if webhook_data.type == "auth": - connection_details = await self._fetch_connection_details( - integration_id, webhook_data.provider - ) - - # Create the integration in the database - integration = await db_client.create_integration( - integration_id=integration_id, - organization_id=organization_id, - provider=webhook_data.provider, - created_by=user_id, - is_active=True, - connection_details=connection_details, - ) - - return { - "status": "success", - "message": f"Integration created successfully with ID: {integration.id}", - } - - async def _fetch_connection_details( - self, connection_id: str, provider_key: str - ) -> Dict[str, Any]: - """ - Fetch connection details from Nango API for a given connection ID. - - Args: - connection_id: The connection ID from the webhook - - Returns: - Connection details as a dictionary - """ - - headers = { - "Authorization": f"Bearer {self.secret_key}", - "Content-Type": "application/json", - } - - url = f"{self.base_url}/connection/{connection_id}/?provider_config_key={provider_key}" - - async with httpx.AsyncClient() as client: - try: - response = await client.get(url, headers=headers) - - if response.status_code != 200: - logger.error( - f"Failed to fetch connection details: {response.status_code} - {response.text}" - ) - raise httpx.HTTPStatusError( - f"Nango API error while fetching connection: {response.status_code}", - request=response.request, - response=response, - ) - - connection_details = response.json() - return connection_details - - except httpx.HTTPError as e: - logger.error(f"HTTP error while fetching connection details: {e}") - # Return empty dict if API call fails, but log the error - return {} - - async def get_access_token( - self, connection_id: str, provider_config_key: str - ) -> Dict[str, Any]: - """ - Get the latest access token for a connection from Nango. - - Args: - connection_id: The connection ID - provider_config_key: The provider config key (e.g., 'google-sheet') - - Returns: - Dict containing access token and other connection details - """ - headers = { - "Authorization": f"Bearer {self.secret_key}", - "Content-Type": "application/json", - } - - url = f"{self.base_url}/connection/{connection_id}?provider_config_key={provider_config_key}" - - async with httpx.AsyncClient() as client: - try: - response = await client.get(url, headers=headers) - - if response.status_code != 200: - logger.error( - f"Failed to get access token: {response.status_code} - {response.text}" - ) - raise httpx.HTTPStatusError( - f"Nango API error: {response.status_code}", - request=response.request, - response=response, - ) - - return response.json() - - except httpx.HTTPError as e: - logger.error(f"HTTP error while getting access token: {e}") - raise - - -# Create a singleton instance -nango_service = NangoService() diff --git a/api/services/integrations/registry.py b/api/services/integrations/registry.py new file mode 100644 index 0000000..e85b6c9 --- /dev/null +++ b/api/services/integrations/registry.py @@ -0,0 +1,128 @@ +from __future__ import annotations + +from typing import Any + +from api.services.integrations.base import ( + IntegrationCompletionContext, + IntegrationNodeRegistration, + IntegrationPackageSpec, + IntegrationRuntimeContext, +) +from api.services.workflow.node_data import BaseNodeData + +_PACKAGE_REGISTRY: dict[str, IntegrationPackageSpec] = {} + + +def register_package(spec: IntegrationPackageSpec) -> IntegrationPackageSpec: + existing = _PACKAGE_REGISTRY.get(spec.name) + if existing is not None and existing is not spec: + raise ValueError( + f"Duplicate integration package registration for {spec.name!r}" + ) + _PACKAGE_REGISTRY[spec.name] = spec + return spec + + +def _ensure_loaded() -> None: + from api.services.integrations.loader import ensure_integrations_loaded + + ensure_integrations_loaded() + + +def all_packages() -> list[IntegrationPackageSpec]: + _ensure_loaded() + return [_PACKAGE_REGISTRY[name] for name in sorted(_PACKAGE_REGISTRY)] + + +def get_package(name: str) -> IntegrationPackageSpec | None: + _ensure_loaded() + return _PACKAGE_REGISTRY.get(name) + + +def get_node_registration(type_name: str) -> IntegrationNodeRegistration | None: + _ensure_loaded() + for package in _PACKAGE_REGISTRY.values(): + for node in package.nodes: + if node.type_name == type_name: + return node + return None + + +def get_node_data_model(type_name: str) -> type[BaseNodeData] | None: + registration = get_node_registration(type_name) + return registration.data_model if registration else None + + +def get_node_spec(type_name: str): + registration = get_node_registration(type_name) + return registration.node_spec if registration else None + + +def get_node_secret_fields(type_name: str) -> tuple[str, ...]: + registration = get_node_registration(type_name) + return registration.sensitive_fields if registration else () + + +def all_node_specs(): + _ensure_loaded() + specs = [] + for package in all_packages(): + specs.extend(node.node_spec for node in package.nodes) + return specs + + +def all_routers(): + _ensure_loaded() + routers = [] + for package in all_packages(): + routers.extend(package.routers) + return routers + + +def create_runtime_sessions( + context: IntegrationRuntimeContext, +): + _ensure_loaded() + sessions = [] + for package in all_packages(): + if package.create_runtime_sessions is None: + continue + sessions.extend(package.create_runtime_sessions(context)) + return sessions + + +def iter_completion_packages( + workflow_definition: dict[str, Any], +): + _ensure_loaded() + nodes = workflow_definition.get("nodes", []) if workflow_definition else [] + for package in all_packages(): + node_types = {node.type_name for node in package.nodes} + package_nodes = [ + node + for node in nodes + if isinstance(node, dict) and node.get("type") in node_types + ] + if package_nodes: + yield package, package_nodes + + +def has_completion_handlers(workflow_definition: dict[str, Any]) -> bool: + return any( + package.run_completion is not None + for package, _nodes in iter_completion_packages(workflow_definition) + ) + + +async def run_completion_handlers( + *, + context: IntegrationCompletionContext, +) -> dict[str, Any]: + results: dict[str, Any] = {} + for package, nodes in iter_completion_packages(context.workflow_definition): + if package.run_completion is None: + continue + package_result = await package.run_completion(nodes, context) + if package_result: + results.update(package_result) + return results diff --git a/api/services/integrations/tuner/__init__.py b/api/services/integrations/tuner/__init__.py new file mode 100644 index 0000000..a37288c --- /dev/null +++ b/api/services/integrations/tuner/__init__.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from api.services.integrations.base import IntegrationPackageSpec +from api.services.integrations.registry import register_package + +from .completion import run_completion +from .node import NODE +from .runtime import create_runtime_sessions + +PACKAGE = register_package( + IntegrationPackageSpec( + name="tuner", + nodes=(NODE,), + create_runtime_sessions=create_runtime_sessions, + run_completion=run_completion, + ) +) + +__all__ = ["PACKAGE"] diff --git a/api/services/integrations/tuner/client.py b/api/services/integrations/tuner/client.py new file mode 100644 index 0000000..18e16cb --- /dev/null +++ b/api/services/integrations/tuner/client.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import Any + +import httpx +from loguru import logger +from pydantic import BaseModel, field_validator + + +class TunerDeliveryConfig(BaseModel): + base_url: str + api_key: str + workspace_id: int + agent_id: str + + @field_validator("api_key", "agent_id") + @classmethod + def _must_not_be_empty(cls, value: str) -> str: + if not value or not value.strip(): + raise ValueError("must not be empty") + return value + + @field_validator("workspace_id") + @classmethod + def _workspace_must_be_positive(cls, value: int) -> int: + if value <= 0: + raise ValueError("must be a positive integer") + return value + + +async def post_call( + config: TunerDeliveryConfig, + payload: dict[str, Any], +) -> dict[str, Any]: + url = ( + f"{config.base_url}/api/v1/public/call" + f"?workspace_id={config.workspace_id}" + f"&agent_remote_identifier={config.agent_id}" + ) + headers = {"Authorization": f"Bearer {config.api_key}"} + + logger.info( + "[tuner] posting completed call {} to workspace {} / agent {}", + payload.get("call_id"), + config.workspace_id, + config.agent_id, + ) + + async with httpx.AsyncClient(timeout=10) as client: + response = await client.post(url, json=payload, headers=headers) + + if response.status_code == 409: + logger.info("[tuner] call {} already exists in tuner", payload.get("call_id")) + return {"status": "duplicate", "status_code": response.status_code} + + if response.status_code >= 400: + logger.error( + "[tuner] POST failed for call {} with status {}: {}", + payload.get("call_id"), + response.status_code, + response.text[:200], + ) + + response.raise_for_status() + + logger.info( + "[tuner] POST succeeded for call {} with status {}", + payload.get("call_id"), + response.status_code, + ) + return {"status": "delivered", "status_code": response.status_code} diff --git a/api/services/integrations/tuner/collector.py b/api/services/integrations/tuner/collector.py new file mode 100644 index 0000000..e55ab85 --- /dev/null +++ b/api/services/integrations/tuner/collector.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import time +from collections import deque +from dataclasses import dataclass +from typing import Any, Callable + +from loguru import logger +from pipecat.frames.frames import ( + BotStartedSpeakingFrame, + BotStoppedSpeakingFrame, + CancelFrame, + EndFrame, + FunctionCallInProgressFrame, + FunctionCallResultFrame, + MetricsFrame, + StartFrame, + UserStartedSpeakingFrame, + UserStoppedSpeakingFrame, + VADUserStoppedSpeakingFrame, +) +from pipecat.observers.base_observer import BaseObserver, FramePushed +from pipecat.observers.turn_tracking_observer import TurnTrackingObserver +from pipecat.observers.user_bot_latency_observer import UserBotLatencyObserver +from pipecat.processors.frame_processor import FrameDirection +from tuner_pipecat_sdk.accumulator import CallAccumulator +from tuner_pipecat_sdk.payload_builder import build_payload + +from api.enums import WorkflowRunMode + +TUNER_RECORDING_PLACEHOLDER = "pipecat://no-recording" + + +@dataclass(frozen=True) +class _PayloadConfig: + call_id: str + call_type: str + recording_url: str + asr_model: str + llm_model: str + tts_model: str + sip_call_id: str | None = None + sip_headers: dict[str, str] | None = None + agent_version: int | None = None + + +def mode_to_tuner_call_type(mode: str | None) -> str: + if mode in { + WorkflowRunMode.WEBRTC.value, + WorkflowRunMode.SMALLWEBRTC.value, + }: + return "web_call" + return "phone_call" + + +class TunerCollector(BaseObserver): + """Collect runtime call metadata and build a deferred Tuner payload.""" + + def __init__( + self, + *, + workflow_run_id: int, + call_type: str, + asr_model: str = "", + llm_model: str = "", + tts_model: str = "", + agent_version: int | None = None, + max_frames: int = 500, + ) -> None: + super().__init__() + self._call_id = str(workflow_run_id) + self._call_type = call_type + self._asr_model = asr_model + self._llm_model = llm_model + self._tts_model = tts_model + self._agent_version = agent_version + self._acc = CallAccumulator() + self._acc.call_start_abs_ns = time.time_ns() + self._context_provider: Callable[[], list[dict[str, Any]]] | None = None + self._processed_frames: set[int] = set() + self._frame_history: deque[int] = deque(maxlen=max_frames) + + def attach_context(self, provider: Callable[[], list[dict[str, Any]]]) -> None: + self._context_provider = provider + + def set_disconnection_reason(self, reason: str | None) -> None: + if reason: + self._acc.set_disconnection_reason(reason) + + def attach_turn_tracking_observer( + self, turn_tracker: TurnTrackingObserver | None + ) -> None: + if turn_tracker is None: + return + + @turn_tracker.event_handler("on_turn_started") + async def _on_turn_started(_tracker: Any, turn_number: int) -> None: + self._acc.on_turn_started(turn_number, time.time_ns()) + + @turn_tracker.event_handler("on_turn_ended") + async def _on_turn_ended( + _tracker: Any, turn_number: int, _duration: float, was_interrupted: bool + ) -> None: + self._acc.on_turn_ended(turn_number, was_interrupted) + + def attach_latency_observer( + self, latency_observer: UserBotLatencyObserver | None + ) -> None: + if latency_observer is None: + return + + @latency_observer.event_handler("on_latency_measured") + async def _on_latency_measured(_observer: Any, latency: float) -> None: + self._acc.on_latency_measured(latency) + + @latency_observer.event_handler("on_latency_breakdown") + async def _on_latency_breakdown(_observer: Any, breakdown: Any) -> None: + self._acc.on_latency_breakdown(breakdown) + + async def on_push_frame(self, data: FramePushed): + if data.direction != FrameDirection.DOWNSTREAM: + return + + if data.frame.id in self._processed_frames: + return + + self._processed_frames.add(data.frame.id) + self._frame_history.append(data.frame.id) + if len(self._processed_frames) > len(self._frame_history): + self._processed_frames = set(self._frame_history) + + frame = data.frame + timestamp_ns = data.timestamp + + if isinstance(frame, StartFrame): + self._acc.on_start(timestamp_ns) + elif isinstance(frame, FunctionCallInProgressFrame): + self._acc.on_function_call_in_progress(frame, timestamp_ns) + elif isinstance(frame, FunctionCallResultFrame): + self._acc.on_function_call_result(frame.tool_call_id, timestamp_ns) + elif isinstance(frame, MetricsFrame): + self._acc.on_metrics_frame(frame) + elif isinstance(frame, UserStartedSpeakingFrame): + self._acc.on_user_started_speaking(timestamp_ns) + elif isinstance(frame, UserStoppedSpeakingFrame): + self._acc.on_user_stopped_speaking(timestamp_ns) + self._acc.on_user_turn_stopped(timestamp_ns) + elif isinstance(frame, BotStartedSpeakingFrame): + self._acc.on_bot_started_speaking(timestamp_ns) + elif isinstance(frame, BotStoppedSpeakingFrame): + self._acc.on_bot_stopped(timestamp_ns) + elif isinstance(frame, VADUserStoppedSpeakingFrame): + self._acc.on_vad_stopped(timestamp_ns) + elif isinstance(frame, (CancelFrame, EndFrame)): + self._acc.on_call_end(timestamp_ns) + + def build_payload_snapshot( + self, + *, + recording_url: str = TUNER_RECORDING_PLACEHOLDER, + ) -> dict[str, Any] | None: + if self._context_provider is None: + logger.warning( + "[tuner] no context provider attached; skipping payload snapshot" + ) + return None + + transcript = list(self._context_provider()) + payload = build_payload( + self._acc, + _PayloadConfig( + call_id=self._call_id, + call_type=self._call_type, + recording_url=recording_url, + asr_model=self._asr_model, + llm_model=self._llm_model, + tts_model=self._tts_model, + agent_version=self._agent_version, + ), + transcript, + ) + return payload.to_dict() diff --git a/api/services/integrations/tuner/completion.py b/api/services/integrations/tuner/completion.py new file mode 100644 index 0000000..f32c738 --- /dev/null +++ b/api/services/integrations/tuner/completion.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import copy +from datetime import UTC, datetime +from typing import Any + +from loguru import logger + +from api.constants import BACKEND_API_ENDPOINT, TUNER_BASE_URL +from api.services.integrations.base import IntegrationCompletionContext + +from .client import TunerDeliveryConfig, post_call +from .collector import TUNER_RECORDING_PLACEHOLDER +from .node import TunerNodeData + + +def _build_recording_url( + context: IntegrationCompletionContext, +) -> str | None: + workflow_run = context.workflow_run + if context.public_token: + base_url = f"{BACKEND_API_ENDPOINT}/api/v1/public/download/workflow/{context.public_token}" + return f"{base_url}/recording" if workflow_run.recording_url else None + return workflow_run.recording_url + + +async def run_completion( + nodes: list[dict[str, Any]], + context: IntegrationCompletionContext, +) -> dict[str, Any]: + results: dict[str, Any] = {} + payload_snapshot = (context.workflow_run.logs or {}).get("tuner_payload") + recording_url = _build_recording_url(context) or TUNER_RECORDING_PLACEHOLDER + + for node in nodes: + node_id = node.get("id", "unknown") + try: + tuner_data = TunerNodeData.model_validate(node.get("data", {})) + except Exception as exc: + logger.warning(f"Tuner node #{node_id} failed validation, skipping: {exc}") + results[f"tuner_{node_id}"] = {"error": "validation_failed"} + continue + + if not tuner_data.tuner_enabled: + logger.debug(f"Tuner node '{tuner_data.name}' is disabled, skipping") + continue + + if not payload_snapshot: + logger.warning( + f"Tuner payload snapshot missing for node '{tuner_data.name}' (#{node_id})" + ) + results[f"tuner_{node_id}"] = {"error": "missing_payload_snapshot"} + continue + + payload = copy.deepcopy(payload_snapshot) + payload["recording_url"] = recording_url + + try: + config = TunerDeliveryConfig( + base_url=TUNER_BASE_URL, + api_key=tuner_data.tuner_api_key or "", + workspace_id=tuner_data.tuner_workspace_id or 0, + agent_id=tuner_data.tuner_agent_id or "", + ) + delivery = await post_call(config, payload) + results[f"tuner_{node_id}"] = { + **delivery, + "workspace_id": tuner_data.tuner_workspace_id, + "agent_id": tuner_data.tuner_agent_id, + "exported_at": datetime.now(UTC).isoformat(), + } + except Exception as exc: + logger.error(f"Tuner export failed for node '{tuner_data.name}': {exc}") + results[f"tuner_{node_id}"] = {"error": str(exc)} + + return results diff --git a/api/services/integrations/tuner/node.py b/api/services/integrations/tuner/node.py new file mode 100644 index 0000000..d603730 --- /dev/null +++ b/api/services/integrations/tuner/node.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from pydantic import model_validator + +from api.services.integrations.base import IntegrationNodeRegistration +from api.services.workflow.node_data import BaseNodeData +from api.services.workflow.node_specs._base import ( + GraphConstraints, + NodeCategory, + NodeExample, + PropertyType, +) +from api.services.workflow.node_specs.model_spec import ( + build_spec, + node_spec, + spec_field, +) + + +@node_spec( + name="tuner", + display_name="Tuner", + description="Export the completed call to Tuner for Agent Observability", + llm_hint=( + "Tuner is a post-call observability export. It does not participate in the " + "conversation graph and should not be connected to other nodes." + ), + category=NodeCategory.integration, + icon="Activity", + examples=[ + NodeExample( + name="tuner_export", + data={ + "name": "Primary Tuner Export", + "tuner_enabled": True, + "tuner_agent_id": "sales-bot-prod", + "tuner_workspace_id": 42, + "tuner_api_key": "tuner_live_xxxxxxxx", + }, + ) + ], + graph_constraints=GraphConstraints( + min_incoming=0, + max_incoming=0, + min_outgoing=0, + max_outgoing=0, + ), + property_order=( + "name", + "tuner_enabled", + "tuner_agent_id", + "tuner_workspace_id", + "tuner_api_key", + ), + field_overrides={ + "name": { + "spec_default": "Tuner", + "description": "Short identifier for this Tuner export configuration.", + }, + "tuner_enabled": { + "display_name": "Enabled", + "description": "When false, Dograh skips exporting this call to Tuner.", + }, + "tuner_agent_id": { + "display_name": "Tuner Agent ID", + "description": "The agent identifier registered in your Tuner workspace.", + "required": True, + }, + "tuner_workspace_id": { + "display_name": "Tuner Workspace ID", + "description": "Your numeric Tuner workspace ID.", + "required": True, + "min_value": 1, + }, + "tuner_api_key": { + "display_name": "Tuner API Key", + "description": "Bearer token used when posting completed calls to Tuner.", + "required": True, + }, + }, +) +class TunerNodeData(BaseNodeData): + tuner_enabled: bool = spec_field( + default=True, + ui_type=PropertyType.boolean, + display_name="Enabled", + description="When false, Dograh skips exporting this call to Tuner.", + ) + tuner_agent_id: str | None = spec_field( + default=None, + ui_type=PropertyType.string, + display_name="Tuner Agent ID", + description="The agent identifier registered in your Tuner workspace.", + ) + tuner_workspace_id: int | None = spec_field( + default=None, + gt=0, + ui_type=PropertyType.number, + display_name="Tuner Workspace ID", + description="Your numeric Tuner workspace ID.", + ) + tuner_api_key: str | None = spec_field( + default=None, + ui_type=PropertyType.string, + display_name="Tuner API Key", + description="Bearer token used when posting completed calls to Tuner.", + ) + + @model_validator(mode="after") + def _validate_enabled_config(self): + if not self.tuner_enabled: + return self + + missing: list[str] = [] + if not self.tuner_agent_id or not self.tuner_agent_id.strip(): + missing.append("tuner_agent_id") + if self.tuner_workspace_id is None: + missing.append("tuner_workspace_id") + if not self.tuner_api_key or not self.tuner_api_key.strip(): + missing.append("tuner_api_key") + + if missing: + fields = ", ".join(missing) + raise ValueError( + f"Tuner node is enabled but missing required fields: {fields}" + ) + + return self + + +SPEC = build_spec(TunerNodeData) + + +NODE = IntegrationNodeRegistration( + type_name="tuner", + data_model=TunerNodeData, + node_spec=SPEC, + sensitive_fields=("tuner_api_key",), +) diff --git a/api/services/integrations/tuner/runtime.py b/api/services/integrations/tuner/runtime.py new file mode 100644 index 0000000..9c8ae08 --- /dev/null +++ b/api/services/integrations/tuner/runtime.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from typing import Any + +from api.services.configuration.registry import ServiceProviders +from api.services.integrations.base import ( + IntegrationRuntimeContext, + IntegrationRuntimeSession, +) + +from .collector import TunerCollector, mode_to_tuner_call_type + + +def _format_model_label(provider: str | None, model: str | None) -> str: + if provider and model: + return f"{provider}/{model}" + if model: + return model + return provider or "" + + +def _resolve_model_labels(context: IntegrationRuntimeContext) -> tuple[str, str, str]: + user_config = context.user_config + + if context.is_realtime and user_config.realtime: + realtime_provider = user_config.realtime.provider + realtime_model = user_config.realtime.model + llm_model = _format_model_label(realtime_provider, realtime_model) + if realtime_provider in { + ServiceProviders.GOOGLE_REALTIME.value, + ServiceProviders.GOOGLE_VERTEX_REALTIME.value, + ServiceProviders.OPENAI_REALTIME.value, + }: + return "", llm_model, "" + return "", llm_model, "" + + return ( + _format_model_label( + getattr(user_config.stt, "provider", None), + getattr(user_config.stt, "model", None), + ), + _format_model_label( + getattr(user_config.llm, "provider", None), + getattr(user_config.llm, "model", None), + ), + _format_model_label( + getattr(user_config.tts, "provider", None), + getattr(user_config.tts, "model", None), + ), + ) + + +class TunerRuntimeSession(IntegrationRuntimeSession): + name = "tuner" + + def __init__(self, collector: TunerCollector) -> None: + self._collector = collector + + def attach(self, task: Any) -> None: + self._collector.attach_turn_tracking_observer(task.turn_tracking_observer) + self._collector.attach_latency_observer(task.user_bot_latency_observer) + task.add_observer(self._collector) + + async def on_call_finished( + self, + *, + gathered_context: dict[str, Any], + ) -> dict[str, Any] | None: + self._collector.set_disconnection_reason( + gathered_context.get("call_disposition") + ) + payload = self._collector.build_payload_snapshot() + if payload is None: + return None + return {"tuner_payload": payload} + + +def create_runtime_sessions( + context: IntegrationRuntimeContext, +) -> list[IntegrationRuntimeSession]: + tuner_nodes = [ + node + for node in context.workflow_graph.nodes.values() + if node.node_type == "tuner" and getattr(node.data, "tuner_enabled", True) + ] + if not tuner_nodes: + return [] + + asr_model, llm_model, tts_model = _resolve_model_labels(context) + + collector = TunerCollector( + workflow_run_id=context.workflow_run_id, + call_type=mode_to_tuner_call_type(context.workflow_run.mode), + asr_model=asr_model, + llm_model=llm_model, + tts_model=tts_model, + agent_version=getattr(context.run_definition, "version_number", None), + ) + collector.attach_context(context.context_messages_provider) + + return [TunerRuntimeSession(collector)] diff --git a/api/services/pipecat/event_handlers.py b/api/services/pipecat/event_handlers.py index 53e9b49..5ae0312 100644 --- a/api/services/pipecat/event_handlers.py +++ b/api/services/pipecat/event_handlers.py @@ -5,6 +5,7 @@ from loguru import logger from api.db import db_client from api.enums import PostHogEvent, WorkflowRunState from api.services.campaign.circuit_breaker import circuit_breaker +from api.services.integrations import IntegrationRuntimeSession from api.services.pipecat.audio_config import AudioConfig from api.services.pipecat.audio_playback import play_audio, play_audio_loop from api.services.pipecat.in_memory_buffers import ( @@ -70,6 +71,7 @@ def register_event_handlers( pre_call_fetch_task: asyncio.Task | None = None, fetch_recording_audio=None, user_provider_id: str | None = None, + integration_runtime_sessions: list[IntegrationRuntimeSession] | None = None, ): """Register all event handlers for transport and task events. @@ -319,6 +321,20 @@ def register_event_handlers( ) # Clean up engine resources (including voicemail detector) + integration_logs: dict[str, object] = {} + for runtime_session in integration_runtime_sessions or []: + try: + session_logs = await runtime_session.on_call_finished( + gathered_context=gathered_context + ) + if session_logs: + integration_logs.update(session_logs) + except Exception as e: + logger.error( + f"Error finalizing integration runtime session '{runtime_session.name}': {e}", + exc_info=True, + ) + await engine.cleanup() # ------------------------------------------------------------------ @@ -368,14 +384,11 @@ def register_event_handlers( ) ) - # Save real-time feedback logs to workflow run + logs_update: dict[str, object] = {} if not in_memory_logs_buffer.is_empty: try: feedback_events = in_memory_logs_buffer.get_events() - await db_client.update_workflow_run( - run_id=workflow_run_id, - logs={"realtime_feedback_events": feedback_events}, - ) + logs_update["realtime_feedback_events"] = feedback_events logger.debug( f"Saved {len(feedback_events)} feedback events to workflow run logs" ) @@ -384,6 +397,17 @@ def register_event_handlers( else: logger.debug("Logs buffer is empty, skipping save") + logs_update.update(integration_logs) + + if logs_update: + try: + await db_client.update_workflow_run( + run_id=workflow_run_id, + logs=logs_update, + ) + except Exception as e: + logger.error(f"Error saving workflow run logs: {e}", exc_info=True) + # Write buffers to temp files and enqueue combined processing task audio_temp_path = None transcript_temp_path = None diff --git a/api/services/pipecat/run_pipeline.py b/api/services/pipecat/run_pipeline.py index 8bfda23..720a558 100644 --- a/api/services/pipecat/run_pipeline.py +++ b/api/services/pipecat/run_pipeline.py @@ -7,6 +7,10 @@ from loguru import logger from api.db import db_client from api.enums import WorkflowRunMode from api.services.configuration.registry import ServiceProviders +from api.services.integrations import ( + IntegrationRuntimeContext, + create_runtime_sessions, +) from api.services.pipecat.audio_config import AudioConfig, create_audio_config from api.services.pipecat.event_handlers import ( register_audio_data_handler, @@ -525,6 +529,18 @@ async def _run_pipeline( # Create pipeline components audio_buffer, context = create_pipeline_components(audio_config) + integration_runtime_sessions = create_runtime_sessions( + IntegrationRuntimeContext( + workflow_run_id=workflow_run_id, + workflow_run=workflow_run, + workflow_graph=workflow_graph, + run_definition=run_definition, + user_config=user_config, + is_realtime=is_realtime, + context_messages_provider=lambda: context.messages, + ) + ) + # Set the context, audio_config, and audio_buffer after creation engine.set_context(context) engine.set_audio_config(audio_config) @@ -717,6 +733,14 @@ async def _run_pipeline( # Create pipeline task with audio configuration task = create_pipeline_task(pipeline, workflow_run_id, audio_config) + for runtime_session in integration_runtime_sessions: + runtime_session.attach(task) + logger.info( + "[integrations] attached runtime session '{}' for workflow run {}", + runtime_session.name, + workflow_run_id, + ) + # Now set the task and transport output on the engine engine.set_task(task) engine.set_transport_output(transport.output()) @@ -781,6 +805,7 @@ async def _run_pipeline( pre_call_fetch_task=pre_call_fetch_task, fetch_recording_audio=fetch_audio, user_provider_id=user_provider_id, + integration_runtime_sessions=integration_runtime_sessions, ) register_audio_data_handler(audio_buffer, workflow_run_id, in_memory_audio_buffer) diff --git a/api/services/workflow/audit.py b/api/services/workflow/audit.py index 6a7dade..8e70384 100644 --- a/api/services/workflow/audit.py +++ b/api/services/workflow/audit.py @@ -7,7 +7,7 @@ script in `api/services/admin_utils/local_exec.py` is the production consumer. """ -from api.services.workflow.node_specs import REGISTRY +from api.services.workflow.node_specs import all_specs def _build_type_rules() -> tuple[set[str], set[str]]: @@ -16,14 +16,14 @@ def _build_type_rules() -> tuple[set[str], set[str]]: (max_incoming == 0).""" src_forbidden: set[str] = set() tgt_forbidden: set[str] = set() - for name, spec in REGISTRY.items(): + for spec in all_specs(): gc = spec.graph_constraints if gc is None: continue if gc.max_outgoing == 0: - src_forbidden.add(name) + src_forbidden.add(spec.name) if gc.max_incoming == 0: - tgt_forbidden.add(name) + tgt_forbidden.add(spec.name) return src_forbidden, tgt_forbidden diff --git a/api/services/workflow/dto.py b/api/services/workflow/dto.py index 795b28b..47fe9d7 100644 --- a/api/services/workflow/dto.py +++ b/api/services/workflow/dto.py @@ -1,7 +1,25 @@ from enum import Enum -from typing import Annotated, Dict, List, Literal, Optional, Union +from typing import Any, Dict, List, Literal, Optional, Union -from pydantic import BaseModel, Field, ValidationError, model_validator +from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator + +from api.services.integrations import ( + all_packages, +) +from api.services.integrations import ( + get_node_data_model as get_integration_node_data_model, +) +from api.services.workflow.node_data import BaseNodeData +from api.services.workflow.node_specs._base import ( + DisplayOptions, + GraphConstraints, + NodeCategory, + NodeExample, + PropertyOption, + PropertyType, +) +from api.services.workflow.node_specs.constants import DEFAULT_QA_SYSTEM_PROMPT +from api.services.workflow.node_specs.model_spec import node_spec, spec_field class NodeType(str, Enum): @@ -26,74 +44,381 @@ class VariableType(str, Enum): class ExtractionVariableDTO(BaseModel): - name: str = Field(..., min_length=1) - type: VariableType - prompt: Optional[str] = None + name: str = spec_field( + ..., + min_length=1, + ui_type=PropertyType.string, + display_name="Variable Name", + description="snake_case identifier used downstream.", + required=True, + ) + type: VariableType = spec_field( + ..., + display_name="Type", + description="Data type of the extracted value.", + required=True, + options=[ + PropertyOption(value="string", label="String"), + PropertyOption(value="number", label="Number"), + PropertyOption(value="boolean", label="Boolean"), + ], + spec_default="string", + ) + prompt: Optional[str] = spec_field( + default=None, + ui_type=PropertyType.string, + display_name="Extraction Hint", + description="Per-variable hint describing what to look for.", + editor="textarea", + ) class CustomHeaderDTO(BaseModel): - key: str - value: str + key: str = spec_field( + ..., + ui_type=PropertyType.string, + display_name="Header Name", + description="HTTP header name (e.g., 'X-Source').", + required=True, + ) + value: str = spec_field( + ..., + ui_type=PropertyType.string, + display_name="Header Value", + description="Header value (supports {{template_variables}}).", + required=True, + ) # ───────────────────────────────────────────────────────────────────────── # Per-type node data classes. # -# Shared fields are factored out as Pydantic mixins; per-type classes -# inherit only the mixins they need so mistyped fields raise at validation -# time and downstream consumers get accurate types. `is_start` / `is_end` -# live on every variant so the WorkflowGraph can identify boundary nodes -# without dispatching on type. +# Shared fields live on `BaseNodeData` in a neutral module so both core and +# integration nodes can inherit the same workflow contract. Per-type classes +# then add only the mixins they need so mistyped fields raise at validation +# time and downstream consumers get accurate types. # ───────────────────────────────────────────────────────────────────────── -class _NodeDataBase(BaseModel): - name: str = Field(..., min_length=1) - is_start: bool = False - is_end: bool = False - - class _PromptedNodeDataMixin(BaseModel): - prompt: Optional[str] = Field(default=None) - is_static: bool = False - allow_interrupt: bool = False - add_global_prompt: bool = True + prompt: Optional[str] = spec_field( + default=None, + ui_type=PropertyType.mention_textarea, + display_name="Prompt", + description="System prompt for this node. Supports {{template_variables}}.", + required=True, + min_length=1, + ) + allow_interrupt: bool = spec_field( + default=False, + ui_type=PropertyType.boolean, + display_name="Allow Interruption", + description="When true, the user can interrupt the agent mid-utterance.", + ) + add_global_prompt: bool = spec_field( + default=True, + ui_type=PropertyType.boolean, + display_name="Add Global Prompt", + description=( + "When true and a Global node exists, prepends the global prompt to this " + "node's prompt at runtime." + ), + ) class _ExtractionNodeDataMixin(BaseModel): - extraction_enabled: bool = False - extraction_prompt: Optional[str] = None - extraction_variables: Optional[list[ExtractionVariableDTO]] = None + extraction_enabled: bool = spec_field( + default=False, + ui_type=PropertyType.boolean, + display_name="Enable Variable Extraction", + description="When true, runs an LLM extraction pass for this node.", + ) + extraction_prompt: Optional[str] = spec_field( + default=None, + ui_type=PropertyType.string, + display_name="Extraction Prompt", + description="Overall instructions guiding variable extraction.", + display_options=DisplayOptions(show={"extraction_enabled": [True]}), + editor="textarea", + ) + extraction_variables: Optional[list[ExtractionVariableDTO]] = spec_field( + default=None, + display_name="Variables to Extract", + description=( + "Each entry declares one variable to capture, with its name, data " + "type, and extraction hint." + ), + display_options=DisplayOptions(show={"extraction_enabled": [True]}), + ) class _ToolDocumentRefsMixin(BaseModel): - tool_uuids: Optional[List[str]] = None - document_uuids: Optional[List[str]] = None - mcp_tool_filters: Optional[Dict[str, List[str]]] = None + tool_uuids: Optional[List[str]] = spec_field( + default=None, + ui_type=PropertyType.tool_refs, + display_name="Tools", + description="Tools this node can invoke.", + llm_hint="List of tool UUIDs from `list_tools`.", + ) + document_uuids: Optional[List[str]] = spec_field( + default=None, + ui_type=PropertyType.document_refs, + display_name="Knowledge Base Documents", + description="Documents this node can reference.", + llm_hint="List of document UUIDs from `list_documents`.", + ) + mcp_tool_filters: Optional[Dict[str, List[str]]] = spec_field( + default=None, + spec_exclude=True, + ) +@node_spec( + name="startCall", + display_name="Start Call", + description="Entry point of the workflow — plays a greeting and opens the conversation.", + llm_hint=( + "The entry point of every workflow (exactly one required). Plays an " + "optional greeting, can fetch context from an external API before the " + "call begins, and executes the first conversational turn." + ), + category=NodeCategory.call_node, + icon="Play", + examples=[ + NodeExample( + name="warm_greeting", + data={ + "name": "Greeting", + "prompt": "Greet warmly and ask the caller's reason for calling.", + "greeting_type": "text", + "greeting": "Hi {{first_name}}, this is Sarah from Acme.", + "allow_interrupt": True, + }, + ) + ], + graph_constraints=GraphConstraints(min_incoming=0, max_incoming=0), + property_order=( + "name", + "greeting_type", + "greeting", + "greeting_recording_id", + "prompt", + "allow_interrupt", + "add_global_prompt", + "delayed_start", + "delayed_start_duration", + "extraction_enabled", + "extraction_prompt", + "extraction_variables", + "tool_uuids", + "document_uuids", + "pre_call_fetch_enabled", + "pre_call_fetch_url", + "pre_call_fetch_credential_uuid", + ), + field_overrides={ + "name": { + "spec_default": "Start Call", + "description": "Short identifier shown in the canvas and call logs.", + }, + "prompt": { + "description": ( + "Agent system prompt for the opening turn. Supports " + "{{template_variables}} from pre-call fetch and the initial context." + ), + "placeholder": "Greet the caller warmly and ask how you can help today.", + }, + "greeting_type": { + "display_name": "Greeting Type", + "description": ( + "Whether the optional greeting is spoken via TTS from text or " + "played from a pre-recorded audio file." + ), + "options": [ + PropertyOption(value="text", label="Text (TTS)"), + PropertyOption(value="audio", label="Pre-recorded Audio"), + ], + "spec_default": "text", + }, + "greeting": { + "display_name": "Greeting Text", + "description": ( + "Text spoken via TTS at the start of the call. Supports " + "{{template_variables}}. Leave empty to skip the greeting." + ), + "display_options": DisplayOptions(show={"greeting_type": ["text"]}), + "placeholder": "Hi {{first_name}}, this is Sarah from Acme.", + "editor": "textarea", + }, + "greeting_recording_id": { + "display_name": "Greeting Recording", + "description": "Pre-recorded audio file played at the start of the call.", + "ui_type": PropertyType.recording_ref, + "llm_hint": ( + "Value is the `recording_id` string. Use the `list_recordings` " + "MCP tool to discover available recordings." + ), + "display_options": DisplayOptions(show={"greeting_type": ["audio"]}), + }, + "allow_interrupt": { + "description": "When true, the user can interrupt the agent mid-utterance.", + }, + "tool_uuids": { + "description": "Tools the agent can invoke during the opening turn.", + }, + "document_uuids": { + "description": "Documents the agent can reference.", + }, + "delayed_start": { + "display_name": "Delayed Start", + "description": ( + "When true, the agent waits before speaking after pickup. Useful " + "for outbound calls where the called party needs a moment to settle." + ), + }, + "delayed_start_duration": { + "display_name": "Delay Duration (seconds)", + "description": "Seconds to wait before the agent speaks. 0.1–10.", + "spec_default": 2.0, + "min_value": 0.1, + "max_value": 10.0, + "display_options": DisplayOptions(show={"delayed_start": [True]}), + }, + "pre_call_fetch_enabled": { + "display_name": "Pre-Call Data Fetch", + "description": ( + "When true, makes a POST request to an external API before the " + "call starts and merges the JSON response into the call context " + "as template variables." + ), + }, + "pre_call_fetch_url": { + "display_name": "Endpoint URL", + "description": ( + "URL the pre-call POST request is sent to. The request body " + "includes caller and called numbers." + ), + "ui_type": PropertyType.url, + "display_options": DisplayOptions(show={"pre_call_fetch_enabled": [True]}), + "placeholder": "https://api.example.com/customer-lookup", + }, + "pre_call_fetch_credential_uuid": { + "display_name": "Authentication", + "description": "Optional credential attached to the pre-call request.", + "ui_type": PropertyType.credential_ref, + "llm_hint": "Credential UUID from `list_credentials`.", + "display_options": DisplayOptions(show={"pre_call_fetch_enabled": [True]}), + }, + }, +) class StartCallNodeData( - _NodeDataBase, + BaseNodeData, _PromptedNodeDataMixin, _ExtractionNodeDataMixin, _ToolDocumentRefsMixin, ): - is_start: bool = True - greeting: Optional[str] = None - greeting_type: Optional[str] = None # 'text' or 'audio' - greeting_recording_id: Optional[str] = None - wait_for_user_response: bool = False - wait_for_user_response_timeout: Optional[float] = None - detect_voicemail: bool = False - delayed_start: bool = False - delayed_start_duration: Optional[float] = None - pre_call_fetch_enabled: bool = False - pre_call_fetch_url: Optional[str] = None - pre_call_fetch_credential_uuid: Optional[str] = None + is_start: bool = spec_field(default=True, spec_exclude=True) + greeting: Optional[str] = spec_field(default=None, ui_type=PropertyType.string) + greeting_type: Optional[str] = spec_field( + default=None, ui_type=PropertyType.options + ) + greeting_recording_id: Optional[str] = spec_field( + default=None, ui_type=PropertyType.recording_ref + ) + delayed_start: bool = spec_field(default=False, ui_type=PropertyType.boolean) + delayed_start_duration: Optional[float] = spec_field( + default=None, ui_type=PropertyType.number + ) + pre_call_fetch_enabled: bool = spec_field( + default=False, ui_type=PropertyType.boolean + ) + pre_call_fetch_url: Optional[str] = spec_field( + default=None, ui_type=PropertyType.url + ) + pre_call_fetch_credential_uuid: Optional[str] = spec_field( + default=None, ui_type=PropertyType.credential_ref + ) +@node_spec( + name="agentNode", + display_name="Agent Node", + description="Conversational step — the LLM runs one focused exchange.", + llm_hint=( + "Mid-call step executed by the LLM. Most workflows are a chain of agent " + "nodes connected by edges that describe transition conditions. Each agent " + "node can invoke tools and reference documents." + ), + category=NodeCategory.call_node, + icon="Headset", + examples=[ + NodeExample( + name="qualify_lead", + data={ + "name": "Qualify Budget", + "prompt": "Ask about budget and timeline. Capture both before transitioning.", + "allow_interrupt": True, + "extraction_enabled": True, + "extraction_prompt": "Extract budget amount and rough timeline.", + "extraction_variables": [ + { + "name": "budget_usd", + "type": "number", + "prompt": "Stated budget in USD", + }, + { + "name": "timeline", + "type": "string", + "prompt": "When they want to start", + }, + ], + }, + ) + ], + graph_constraints=GraphConstraints(min_incoming=1), + property_order=( + "name", + "prompt", + "allow_interrupt", + "add_global_prompt", + "extraction_enabled", + "extraction_prompt", + "extraction_variables", + "tool_uuids", + "document_uuids", + ), + field_overrides={ + "name": { + "spec_default": "Agent", + "description": ( + "Short identifier for this step (e.g., 'Qualify Budget'). Appears " + "in call logs and edge transition tools." + ), + }, + "prompt": { + "description": ( + "Agent system prompt for this step. Supports {{template_variables}} " + "from extraction or pre-call fetch." + ), + "placeholder": "Ask the caller about their budget and timeline.", + }, + "allow_interrupt": { + "description": ( + "When true, the user can interrupt the agent mid-utterance. Set " + "false for non-interruptible disclosures." + ), + "spec_default": True, + }, + "tool_uuids": { + "description": "Tools the agent can invoke during this step.", + }, + "document_uuids": { + "description": "Documents the agent can reference during this step.", + }, + }, +) class AgentNodeData( - _NodeDataBase, + BaseNodeData, _PromptedNodeDataMixin, _ExtractionNodeDataMixin, _ToolDocumentRefsMixin, @@ -101,43 +426,450 @@ class AgentNodeData( pass +@node_spec( + name="endCall", + display_name="End Call", + description="Closes the conversation and hangs up.", + llm_hint=( + "Terminal node that politely closes the conversation. Variable extraction " + "can run before hangup. A workflow can have multiple endCall nodes reached " + "via different edge conditions." + ), + category=NodeCategory.call_node, + icon="OctagonX", + examples=[ + NodeExample( + name="successful_close", + data={ + "name": "Successful Close", + "prompt": "Confirm the appointment time, thank the caller, and end the call.", + "add_global_prompt": False, + }, + ) + ], + graph_constraints=GraphConstraints(min_incoming=1, min_outgoing=0, max_outgoing=0), + property_order=( + "name", + "prompt", + "add_global_prompt", + "extraction_enabled", + "extraction_prompt", + "extraction_variables", + ), + field_overrides={ + "name": { + "spec_default": "End Call", + "description": ( + "Short identifier shown in call logs. Should describe the ending " + "context (e.g., 'Successful close', 'Polite decline')." + ), + }, + "prompt": { + "description": ( + "Agent system prompt for the closing exchange. Supports " + "{{template_variables}} from extraction or pre-call fetch." + ), + "placeholder": "Thank the caller and confirm next steps before ending the call.", + }, + "allow_interrupt": {"spec_exclude": True}, + "add_global_prompt": { + "description": ( + "When true and a Global node exists, prepends the global prompt " + "to this node's prompt at runtime." + ), + "spec_default": False, + }, + "extraction_enabled": { + "description": ( + "When true, runs an LLM extraction pass before hangup to capture " + "variables from the conversation." + ) + }, + "extraction_prompt": { + "description": ( + "Overall instructions guiding how variables should be extracted " + "from the conversation." + ) + }, + "extraction_variables": { + "description": ( + "Each entry declares one variable to capture from the conversation, " + "with its name, data type, and a per-variable extraction hint." + ) + }, + }, +) class EndCallNodeData( - _NodeDataBase, + BaseNodeData, _PromptedNodeDataMixin, _ExtractionNodeDataMixin, ): - is_end: bool = True + is_end: bool = spec_field(default=True, spec_exclude=True) -class GlobalNodeData(_NodeDataBase, _PromptedNodeDataMixin): +@node_spec( + name="globalNode", + display_name="Global Node", + description="Persona/tone appended to every agent node's prompt.", + llm_hint=( + "System-level prompt appended to every prompted node whose " + "`add_global_prompt` is true. Use it for persona, tone, and shared " + "rules that apply across the entire conversation. At most one global " + "node per workflow." + ), + category=NodeCategory.global_node, + icon="Globe", + examples=[ + NodeExample( + name="basic_persona", + description="Establishes a consistent persona across the call.", + data={ + "name": "Persona", + "prompt": ( + "You are Sarah, a polite and warm representative from Acme Corp. " + "Always thank the caller for their time and speak in short " + "conversational sentences." + ), + }, + ) + ], + graph_constraints=GraphConstraints( + min_incoming=0, + max_incoming=0, + min_outgoing=0, + max_outgoing=0, + ), + property_order=("name", "prompt"), + field_overrides={ + "name": { + "spec_default": "Global Node", + "description": ( + "Short identifier shown in the canvas and call logs. Has no " + "runtime effect." + ), + }, + "prompt": { + "display_name": "Global Prompt", + "description": ( + "Text appended to every prompted node's system prompt when that " + "node has `add_global_prompt=true`. Supports {{template_variables}}." + ), + "placeholder": ( + "You are a friendly assistant calling on behalf of {{company_name}}." + ), + "spec_default": ( + "You are a helpful assistant whose mode of interaction with the " + "user is voice. So don't use any special characters which can not " + "be pronounced. Use short sentences and simple language." + ), + }, + "allow_interrupt": {"spec_exclude": True}, + "add_global_prompt": {"spec_exclude": True}, + }, +) +class GlobalNodeData(BaseNodeData, _PromptedNodeDataMixin): pass -class TriggerNodeData(_NodeDataBase): - trigger_path: Optional[str] = None - enabled: bool = True +@node_spec( + name="trigger", + display_name="API Trigger", + description="Public HTTP endpoints that launch the workflow.", + llm_hint=( + "Exposes two public HTTP POST endpoints derived from the auto-generated " + "`trigger_path`:\n" + " • Production: `/api/v1/public/agent/` — runs " + "the published agent. Use this from production systems.\n" + " • Test: `/api/v1/public/agent/test/` — runs " + "the latest draft, useful for verifying changes before publishing. Falls " + "back to the published agent when no draft exists.\n" + "Both require an API key in the `X-API-Key` header.\n" + "Request body fields:\n" + " • `phone_number` (string, required) — destination to dial.\n" + " • `initial_context` (object, optional) — merged into the run's initial context.\n" + " • `telephony_configuration_id` (int, optional) — pick a specific telephony " + "configuration for the call. Must belong to the same organization as the " + "trigger. When omitted, the org's default outbound configuration is used." + ), + category=NodeCategory.trigger, + icon="Webhook", + examples=[ + NodeExample(name="default", data={"name": "Inbound Trigger", "enabled": True}) + ], + graph_constraints=GraphConstraints(min_incoming=0, max_incoming=0), + property_order=("name", "enabled", "trigger_path"), + field_overrides={ + "name": { + "spec_default": "API Trigger", + "description": "Short identifier shown in the canvas. No runtime effect.", + }, + "enabled": { + "display_name": "Enabled", + "description": "When false, the trigger URL returns 404.", + }, + "trigger_path": { + "display_name": "Trigger Path", + "description": ( + "Auto-generated UUID-style path segment that uniquely identifies " + "this trigger. Used in both URLs:\n" + " • Production: `/api/v1/public/agent/` — executes " + "the published agent.\n" + " • Test: `/api/v1/public/agent/test/` — executes " + "the latest draft.\n" + "Do not edit manually." + ), + }, + }, +) +class TriggerNodeData(BaseNodeData): + trigger_path: Optional[str] = spec_field(default=None, ui_type=PropertyType.string) + enabled: bool = spec_field(default=True, ui_type=PropertyType.boolean) -class WebhookNodeData(_NodeDataBase): - enabled: bool = True - http_method: Optional[str] = None - endpoint_url: Optional[str] = None - credential_uuid: Optional[str] = None - custom_headers: Optional[list[CustomHeaderDTO]] = None - payload_template: Optional[dict] = None +@node_spec( + name="webhook", + display_name="Webhook", + description="Send HTTP request after the workflow completes.", + llm_hint=( + "Sends an HTTP request to an external system after the workflow completes. " + "The payload is a Jinja-templated JSON body with access to " + "`workflow_run_id`, `initial_context`, `gathered_context`, `annotations`, " + "and call metadata." + ), + category=NodeCategory.integration, + icon="Link2", + examples=[ + NodeExample( + name="post_to_crm", + data={ + "name": "Notify CRM", + "enabled": True, + "http_method": "POST", + "endpoint_url": "https://crm.example.com/calls", + "payload_template": { + "run_id": "{{workflow_run_id}}", + "outcome": "{{gathered_context.call_disposition}}", + }, + }, + ) + ], + graph_constraints=GraphConstraints( + min_incoming=0, max_incoming=0, min_outgoing=0, max_outgoing=0 + ), + property_order=( + "name", + "enabled", + "http_method", + "endpoint_url", + "credential_uuid", + "custom_headers", + "payload_template", + ), + field_overrides={ + "name": { + "spec_default": "Webhook", + "description": "Short identifier shown in the canvas and run logs.", + }, + "enabled": { + "display_name": "Enabled", + "description": "When false, the webhook is skipped at run time.", + }, + "http_method": { + "display_name": "HTTP Method", + "description": "HTTP verb used for the outbound request.", + "options": [ + PropertyOption(value="GET", label="GET"), + PropertyOption(value="POST", label="POST"), + PropertyOption(value="PUT", label="PUT"), + PropertyOption(value="PATCH", label="PATCH"), + PropertyOption(value="DELETE", label="DELETE"), + ], + "spec_default": "POST", + }, + "endpoint_url": { + "display_name": "Endpoint URL", + "description": "URL the request is sent to.", + "ui_type": PropertyType.url, + "placeholder": "https://api.example.com/webhook", + }, + "credential_uuid": { + "display_name": "Authentication", + "description": "Optional credential applied as the Authorization header.", + "ui_type": PropertyType.credential_ref, + "llm_hint": "Credential UUID from `list_credentials`.", + }, + "custom_headers": { + "display_name": "Custom Headers", + "description": "Additional HTTP headers to include with the request.", + }, + "payload_template": { + "display_name": "Payload Template", + "description": ( + "JSON body of the request. Values are Jinja-rendered against the " + "run context — `{{workflow_run_id}}`, `{{gathered_context.foo}}`, " + "`{{annotations.qa_xxx}}`, etc." + ), + "ui_type": PropertyType.json, + "spec_default": { + "call_id": "{{workflow_run_id}}", + "first_name": "{{initial_context.first_name}}", + "rsvp": "{{gathered_context.rsvp}}", + "duration": "{{cost_info.call_duration_seconds}}", + "recording_url": "{{recording_url}}", + "transcript_url": "{{transcript_url}}", + }, + }, + }, +) +class WebhookNodeData(BaseNodeData): + enabled: bool = spec_field(default=True, ui_type=PropertyType.boolean) + http_method: Optional[str] = spec_field(default=None, ui_type=PropertyType.options) + endpoint_url: Optional[str] = spec_field(default=None, ui_type=PropertyType.url) + credential_uuid: Optional[str] = spec_field( + default=None, ui_type=PropertyType.credential_ref + ) + custom_headers: Optional[list[CustomHeaderDTO]] = spec_field(default=None) + payload_template: Optional[dict] = spec_field( + default=None, ui_type=PropertyType.json + ) -class QANodeData(_NodeDataBase): - qa_enabled: bool = True - qa_use_workflow_llm: bool = True - qa_provider: Optional[str] = None - qa_model: Optional[str] = None - qa_api_key: Optional[str] = None - qa_endpoint: Optional[str] = None - qa_system_prompt: Optional[str] = None - qa_min_call_duration: int = 15 - qa_voicemail_calls: bool = False - qa_sample_rate: int = 100 +@node_spec( + name="qa", + display_name="QA Analysis", + description="Run LLM quality analysis on the call transcript.", + llm_hint=( + "Runs an LLM quality review on the call transcript after completion. " + "Per-node analysis splits the conversation by node and evaluates each " + "segment against the configured system prompt. Sampling, minimum " + "duration, and voicemail filters are supported." + ), + category=NodeCategory.integration, + icon="ClipboardCheck", + examples=[ + NodeExample( + name="basic_qa", + data={ + "name": "Compliance Check", + "qa_enabled": True, + "qa_system_prompt": ( + "You are a compliance reviewer. Review the transcript and " + "produce a JSON object with `tags`, `summary`, " + "`call_quality_score`, and `overall_sentiment`." + ), + "qa_min_call_duration": 30, + "qa_sample_rate": 100, + }, + ) + ], + graph_constraints=GraphConstraints( + min_incoming=0, max_incoming=0, min_outgoing=0, max_outgoing=0 + ), + property_order=( + "name", + "qa_enabled", + "qa_system_prompt", + "qa_min_call_duration", + "qa_voicemail_calls", + "qa_sample_rate", + "qa_use_workflow_llm", + "qa_provider", + "qa_model", + "qa_api_key", + "qa_endpoint", + ), + field_overrides={ + "name": { + "spec_default": "QA Analysis", + "description": "Short identifier for this QA configuration.", + }, + "qa_enabled": { + "display_name": "Enabled", + "description": "When false, the QA run is skipped.", + }, + "qa_system_prompt": { + "display_name": "System Prompt", + "description": ( + "Instructions to the QA reviewer LLM. Supports placeholders: " + "`{node_summary}`, `{previous_conversation_summary}`, " + "`{transcript}`, `{metrics}`." + ), + "spec_default": DEFAULT_QA_SYSTEM_PROMPT, + "editor": "textarea", + }, + "qa_min_call_duration": { + "display_name": "Minimum Call Duration (seconds)", + "description": "Calls shorter than this are skipped.", + "min_value": 0, + }, + "qa_voicemail_calls": { + "display_name": "Include Voicemail Calls", + "description": "When false, calls flagged as voicemail are skipped.", + }, + "qa_sample_rate": { + "display_name": "Sample Rate (%)", + "description": ( + "Percent of eligible calls QA'd. 100 means every call; lower " + "values use random sampling." + ), + "min_value": 1, + "max_value": 100, + }, + "qa_use_workflow_llm": { + "display_name": "Use Workflow's LLM", + "description": ( + "When true, the QA pass uses the same LLM the workflow runs with. " + "Set false to specify a separate provider/model." + ), + }, + "qa_provider": { + "display_name": "QA LLM Provider", + "description": "LLM provider used for the QA pass.", + "options": [ + PropertyOption(value="openai", label="OpenAI"), + PropertyOption(value="azure", label="Azure OpenAI"), + PropertyOption(value="openrouter", label="OpenRouter"), + PropertyOption(value="anthropic", label="Anthropic"), + ], + "display_options": DisplayOptions(show={"qa_use_workflow_llm": [False]}), + }, + "qa_model": { + "display_name": "QA Model", + "description": ( + "Model identifier (e.g., 'gpt-4o', 'claude-sonnet-4-6'). " + "Provider-specific." + ), + "spec_default": "default", + "display_options": DisplayOptions(show={"qa_use_workflow_llm": [False]}), + }, + "qa_api_key": { + "display_name": "API Key", + "description": "API key for the chosen provider.", + "display_options": DisplayOptions(show={"qa_use_workflow_llm": [False]}), + }, + "qa_endpoint": { + "display_name": "Azure Endpoint", + "description": "Required for the Azure provider.", + "ui_type": PropertyType.url, + "display_options": DisplayOptions( + show={"qa_use_workflow_llm": [False], "qa_provider": ["azure"]} + ), + }, + }, +) +class QANodeData(BaseNodeData): + qa_enabled: bool = spec_field(default=True, ui_type=PropertyType.boolean) + qa_use_workflow_llm: bool = spec_field(default=True, ui_type=PropertyType.boolean) + qa_provider: Optional[str] = spec_field(default=None, ui_type=PropertyType.options) + qa_model: Optional[str] = spec_field(default=None, ui_type=PropertyType.string) + qa_api_key: Optional[str] = spec_field(default=None, ui_type=PropertyType.string) + qa_endpoint: Optional[str] = spec_field(default=None, ui_type=PropertyType.url) + qa_system_prompt: Optional[str] = spec_field( + default=None, ui_type=PropertyType.string + ) + qa_min_call_duration: int = spec_field(default=15, ui_type=PropertyType.number) + qa_voicemail_calls: bool = spec_field(default=False, ui_type=PropertyType.boolean) + qa_sample_rate: int = spec_field(default=100, ui_type=PropertyType.number) # Union of every per-type data class — useful as a type annotation on @@ -157,9 +889,9 @@ NodeDataDTO = Union[ # ───────────────────────────────────────────────────────────────────────── # Per-type RF nodes. # -# RFNodeDTO is a discriminated Union over `type`. Pydantic dispatches to -# the right variant when validating wire JSON. Direct instantiation must -# use the concrete per-type class (StartCallRFNode, AgentRFNode, ...). +# Core node variants keep concrete helper classes for tests and type-aware +# consumers. The persisted workflow DTO itself validates `type` dynamically +# against the core registry plus any integration packages. # ───────────────────────────────────────────────────────────────────────── @@ -229,18 +961,38 @@ class QARFNode(_RFNodeBase): data: QANodeData -RFNodeDTO = Annotated[ - Union[ - StartCallRFNode, - AgentRFNode, - EndCallRFNode, - GlobalRFNode, - TriggerRFNode, - WebhookRFNode, - QARFNode, - ], - Field(discriminator="type"), -] +_PROMPT_REQUIRED_NODE_TYPES: dict[str, str] = { + NodeType.startNode.value: "start", + NodeType.agentNode.value: "agent", + NodeType.endNode.value: "end", + NodeType.globalNode.value: "global", +} + + +class RFNodeDTO(_RFNodeBase): + type: str = Field(..., min_length=1) + data: Any + + @field_validator("type") + @classmethod + def _validate_type(cls, value: str) -> str: + if get_node_data_model(value) is None: + raise ValueError(f"Unknown node type: {value!r}") + return value + + @model_validator(mode="after") + def _validate(self): + data_model = get_node_data_model(self.type) + if data_model is None: + raise ValueError(f"Unknown node type: {self.type!r}") + + self.data = data_model.model_validate(self.data) + + prompt_label = _PROMPT_REQUIRED_NODE_TYPES.get(self.type) + if prompt_label: + _require_prompt(self.data, prompt_label) + + return self # ───────────────────────────────────────────────────────────────────────── @@ -294,9 +1046,7 @@ class ReactFlowDTO(BaseModel): return self -# Node type → per-type data class. Keeps sanitize_workflow_definition in -# step with RFNodeDTO's discriminated union. -_NODE_DATA_CLASSES: dict[str, type[BaseModel]] = { +_CORE_NODE_DATA_CLASSES: dict[str, type[BaseNodeData]] = { NodeType.startNode.value: StartCallNodeData, NodeType.agentNode.value: AgentNodeData, NodeType.endNode.value: EndCallNodeData, @@ -307,6 +1057,18 @@ _NODE_DATA_CLASSES: dict[str, type[BaseModel]] = { } +def get_node_data_model(type_name: str) -> type[BaseNodeData] | None: + return _CORE_NODE_DATA_CLASSES.get(type_name) or get_integration_node_data_model( + type_name + ) + + +def all_node_type_names() -> set[str]: + return set(_CORE_NODE_DATA_CLASSES) | { + node.type_name for package in all_packages() for node in package.nodes + } + + def sanitize_workflow_definition(definition: dict | None) -> dict | None: """Strip unknown fields from each node.data and edge.data so UI-only runtime state (`invalid`, `validationMessage`, etc.) doesn't leak into @@ -333,7 +1095,7 @@ def sanitize_workflow_definition(definition: dict | None) -> dict | None: def _sanitize_node(node): if not isinstance(node, dict): return node - data_cls = _NODE_DATA_CLASSES.get(node.get("type")) + data_cls = get_node_data_model(node.get("type")) raw_data = node.get("data") if not data_cls or not isinstance(raw_data, dict): return node diff --git a/api/services/workflow/node_data.py b/api/services/workflow/node_data.py new file mode 100644 index 0000000..deb9638 --- /dev/null +++ b/api/services/workflow/node_data.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from pydantic import BaseModel + +from api.services.workflow.node_specs._base import PropertyType +from api.services.workflow.node_specs.model_spec import spec_field + + +class BaseNodeData(BaseModel): + name: str = spec_field( + ..., + min_length=1, + ui_type=PropertyType.string, + display_name="Name", + description="Short identifier shown in the canvas and call logs.", + required=True, + ) + is_start: bool = spec_field(default=False, spec_exclude=True) + is_end: bool = spec_field(default=False, spec_exclude=True) diff --git a/api/services/workflow/node_specs/__init__.py b/api/services/workflow/node_specs/__init__.py index 93f6ab0..620fb49 100644 --- a/api/services/workflow/node_specs/__init__.py +++ b/api/services/workflow/node_specs/__init__.py @@ -1,10 +1,8 @@ """Node specification registry. -Adding a new node type: -1. Create a new module under this package, define a `SPEC: NodeSpec`. -2. Add it to the imports + REGISTRY below. -3. The Pydantic discriminated-union variant in dto.py must use the same - `name` value as `SPEC.name`. +Core node specs are generated from the workflow DTO models. Third-party +integration node specs live under `api.services.integrations//` and +register through the integration registry so they don't need edits here. """ from __future__ import annotations @@ -21,8 +19,10 @@ from api.services.workflow.node_specs._base import ( PropertyType, evaluate_display_options, ) +from api.services.workflow.node_specs.model_spec import build_spec REGISTRY: dict[str, NodeSpec] = {} +_CORE_SPECS_LOADED = False def register(spec: NodeSpec) -> NodeSpec: @@ -38,12 +38,23 @@ def register(spec: NodeSpec) -> NodeSpec: def get_spec(name: str) -> NodeSpec | None: - return REGISTRY.get(name) + _ensure_core_registered() + if name in REGISTRY: + return REGISTRY[name] + + from api.services.integrations import get_node_spec + + return get_node_spec(name) def all_specs() -> list[NodeSpec]: """All registered specs, sorted by name for stable output.""" - return [REGISTRY[name] for name in sorted(REGISTRY)] + _ensure_core_registered() + from api.services.integrations import all_node_specs + + specs = {spec.name: spec for spec in REGISTRY.values()} + specs.update({spec.name: spec for spec in all_node_specs()}) + return [specs[name] for name in sorted(specs)] __all__ = [ @@ -64,19 +75,15 @@ __all__ = [ ] -# Side-effect imports — each module's `register(SPEC)` call populates REGISTRY. -# Keep at module bottom so the registry helpers are defined first. -from api.services.workflow.node_specs import ( # noqa: E402, F401 - agent, - end_call, - global_node, - qa, - start_call, - trigger, - webhook, -) +def _ensure_core_registered() -> None: + global _CORE_SPECS_LOADED + if _CORE_SPECS_LOADED: + return -# Wire up registrations from the SPEC constants in each module. -for _module in (start_call, agent, end_call, global_node, trigger, webhook, qa): - register(_module.SPEC) -del _module + from api.services.workflow.dto import _CORE_NODE_DATA_CLASSES + + for model_cls in _CORE_NODE_DATA_CLASSES.values(): + if model_cls.__node_spec_metadata__.name in REGISTRY: + continue + register(build_spec(model_cls)) + _CORE_SPECS_LOADED = True diff --git a/api/services/workflow/node_specs/_base.py b/api/services/workflow/node_specs/_base.py index cbf044f..b8324c1 100644 --- a/api/services/workflow/node_specs/_base.py +++ b/api/services/workflow/node_specs/_base.py @@ -1,9 +1,9 @@ """Spec schema for node definitions. -A `NodeSpec` is the single source of truth for a node type. It drives: -- Pydantic validation (the per-type DTOs in dto.py mirror these property types) -- The generic UI renderer (frontend reads specs via /api/v1/node-types) -- The LLM SDK (constructors and JSON-Schema derived from these specs) +`NodeSpec` is the serialized contract exposed to the frontend, MCP tools, and +SDKs. Core workflow node specs are generated from the DTO models plus +model-attached metadata; integration packages may generate them the same way or +register a prebuilt spec object. Every property's `description` is LLM-readable copy — treat it as production documentation, not internal notes. Spec lint enforces non-empty descriptions @@ -122,6 +122,16 @@ class PropertyOption(BaseModel): model_config = ConfigDict(extra="forbid") + def to_mcp_dict(self) -> dict[str, Any]: + """Lean projection for `get_node_type`: the `value` an LLM writes in + code, plus a `description` when one carries real meaning. The UI + `label` is dropped — it's the option's display string, never used + when authoring.""" + out: dict[str, Any] = {"value": self.value} + if self.description: + out["description"] = self.description + return out + class PropertySpec(BaseModel): """Single field on a node. @@ -175,6 +185,43 @@ class PropertySpec(BaseModel): model_config = ConfigDict(extra="forbid") + def to_mcp_dict(self) -> dict[str, Any]: + """Lean projection of this property for the `get_node_type` MCP tool. + + Keeps only what an LLM needs to author a valid value: name, type, + description, llm_hint, requiredness, default, enum options, nested + row properties, and validation bounds. UI-rendering concerns + (`display_name`, `placeholder`, `display_options`, `editor`, + `extra`) and null/empty fields are omitted — they're noise in the + model's context and never appear in authored SDK code. + """ + out: dict[str, Any] = { + "name": self.name, + "type": self.type.value, + "description": self.description, + } + if self.llm_hint: + out["llm_hint"] = self.llm_hint + if self.required: + out["required"] = True + if self.default is not None: + out["default"] = self.default + if self.options: + out["options"] = [opt.to_mcp_dict() for opt in self.options] + if self.properties: + out["properties"] = [prop.to_mcp_dict() for prop in self.properties] + for constraint in ( + "min_value", + "max_value", + "min_length", + "max_length", + "pattern", + ): + value = getattr(self, constraint) + if value is not None: + out[constraint] = value + return out + PropertySpec.model_rebuild() @@ -222,3 +269,33 @@ class NodeSpec(BaseModel): graph_constraints: Optional[GraphConstraints] = None model_config = ConfigDict(extra="forbid") + + def to_mcp_dict(self) -> dict[str, Any]: + """Lean projection of this spec for the `get_node_type` MCP tool. + + Drops node-level UI metadata (`display_name`, `category`, `icon`, + `version`) and the per-property rendering concerns trimmed by + `PropertySpec.to_mcp_dict`, leaving just the authoring-relevant + schema the LLM consumes when composing a workflow. The full spec is + still served verbatim to the frontend renderer (REST `node-types` + route) and the SDK codegen / TS validator (`ts_bridge`), which need + the dropped fields. + """ + out: dict[str, Any] = { + "name": self.name, + "description": self.description, + } + if self.llm_hint: + out["llm_hint"] = self.llm_hint + out["properties"] = [prop.to_mcp_dict() for prop in self.properties] + if self.examples: + out["examples"] = [ + ex.model_dump(mode="json", exclude_none=True) for ex in self.examples + ] + if self.graph_constraints: + constraints = self.graph_constraints.model_dump( + mode="json", exclude_none=True + ) + if constraints: + out["graph_constraints"] = constraints + return out diff --git a/api/services/workflow/node_specs/agent.py b/api/services/workflow/node_specs/agent.py deleted file mode 100644 index 218fd9f..0000000 --- a/api/services/workflow/node_specs/agent.py +++ /dev/null @@ -1,168 +0,0 @@ -"""Spec for the Agent node — the workhorse mid-call node where the LLM -executes a focused conversational step with optional tools and documents.""" - -from api.services.workflow.node_specs._base import ( - DisplayOptions, - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertyOption, - PropertySpec, - PropertyType, -) - -SPEC = NodeSpec( - name="agentNode", - display_name="Agent Node", - description="Conversational step — the LLM runs one focused exchange.", - llm_hint=( - "Mid-call step executed by the LLM. Most workflows are a chain of " - "agent nodes connected by edges that describe transition conditions. " - "Each agent node can invoke tools and reference documents." - ), - category=NodeCategory.call_node, - icon="Headset", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description=( - "Short identifier for this step (e.g., 'Qualify Budget'). " - "Appears in call logs and edge transition tools." - ), - required=True, - min_length=1, - default="Agent", - ), - PropertySpec( - name="prompt", - type=PropertyType.mention_textarea, - display_name="Prompt", - description=( - "Agent system prompt for this step. Supports " - "{{template_variables}} from extraction or pre-call fetch." - ), - required=True, - min_length=1, - placeholder="Ask the caller about their budget and timeline.", - ), - PropertySpec( - name="allow_interrupt", - type=PropertyType.boolean, - display_name="Allow Interruption", - description=( - "When true, the user can interrupt the agent mid-utterance. " - "Set false for non-interruptible disclosures." - ), - default=True, - ), - PropertySpec( - name="add_global_prompt", - type=PropertyType.boolean, - display_name="Add Global Prompt", - description=( - "When true and a Global node exists, prepends the global " - "prompt to this node's prompt at runtime." - ), - default=True, - ), - PropertySpec( - name="extraction_enabled", - type=PropertyType.boolean, - display_name="Enable Variable Extraction", - description=( - "When true, runs an LLM extraction pass on transition out of " - "this node to capture variables from the conversation." - ), - default=False, - ), - PropertySpec( - name="extraction_prompt", - type=PropertyType.string, - display_name="Extraction Prompt", - description="Overall instructions guiding variable extraction.", - display_options=DisplayOptions(show={"extraction_enabled": [True]}), - editor="textarea", - ), - PropertySpec( - name="extraction_variables", - type=PropertyType.fixed_collection, - display_name="Variables to Extract", - description=( - "Each entry declares one variable to capture from the " - "conversation, with its name, type, and per-variable hint." - ), - display_options=DisplayOptions(show={"extraction_enabled": [True]}), - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Variable Name", - description="snake_case identifier used downstream.", - required=True, - ), - PropertySpec( - name="type", - type=PropertyType.options, - display_name="Type", - description="Data type of the extracted value.", - required=True, - default="string", - options=[ - PropertyOption(value="string", label="String"), - PropertyOption(value="number", label="Number"), - PropertyOption(value="boolean", label="Boolean"), - ], - ), - PropertySpec( - name="prompt", - type=PropertyType.string, - display_name="Extraction Hint", - description="Per-variable hint describing what to look for.", - editor="textarea", - ), - ], - ), - PropertySpec( - name="tool_uuids", - type=PropertyType.tool_refs, - display_name="Tools", - description="Tools the agent can invoke during this step.", - llm_hint="List of tool UUIDs from `list_tools`.", - ), - PropertySpec( - name="document_uuids", - type=PropertyType.document_refs, - display_name="Knowledge Base Documents", - description="Documents the agent can reference during this step.", - llm_hint="List of document UUIDs from `list_documents`.", - ), - ], - examples=[ - NodeExample( - name="qualify_lead", - data={ - "name": "Qualify Budget", - "prompt": "Ask about budget and timeline. Capture both before transitioning.", - "allow_interrupt": True, - "extraction_enabled": True, - "extraction_prompt": "Extract budget amount and rough timeline.", - "extraction_variables": [ - { - "name": "budget_usd", - "type": "number", - "prompt": "Stated budget in USD", - }, - { - "name": "timeline", - "type": "string", - "prompt": "When they want to start", - }, - ], - }, - ), - ], - graph_constraints=GraphConstraints(min_incoming=1), -) diff --git a/api/services/workflow/node_specs/constants.py b/api/services/workflow/node_specs/constants.py new file mode 100644 index 0000000..c5b7544 --- /dev/null +++ b/api/services/workflow/node_specs/constants.py @@ -0,0 +1,44 @@ +DEFAULT_QA_SYSTEM_PROMPT = """You are a QA analyst evaluating a specific segment of a voice AI conversation. + +## Node Purpose +{{node_summary}} + +## Previous Conversation Context (For start of conversation, previous conversation summary can be empty.) +{{previous_conversation_summary}} + +## Tags to evaluate + +Examine the conversation carefully and identify which of the following tags apply: + +- UNCLEAR_CONVERSATION - The conversation is not coherent or clear, messages don't connect logically +- ASSISTANT_IN_LOOP - The assistant asks the same question multiple times or gets stuck repeating itself +- ASSISTANT_REPLY_IMPROPER - The assistant did not reply properly to the user's question/query or seems confused by what the user said +- USER_FRUSTRATED - The user seems angry, frustrated, or is complaining about something in the call +- USER_NOT_UNDERSTANDING - The user explicitly says they don't understand or repeatedly asks for clarification +- HEARING_ISSUES - Either party can't hear the other ("hello?", "are you there?", "can you hear me?") +- DEAD_AIR - Unusually long silences in the conversation (use the timestamps to judge) +- USER_REQUESTING_FEATURE - The user asks for something the assistant can't fulfill +- ASSISTANT_LACKS_EMPATHY - The assistant ignores the user's personal situation or emotional state and continues pitching or pushing the agenda. +- USER_DETECTS_AI - The user suspects or identifies that they are talking to an AI/robot/bot rather than a real human. + +## Call metrics (pre-computed) + +Use these alongside the transcript for your analysis: +{{metrics}} + +## Output format + +Return ONLY a valid JSON object (no markdown): +{ + "tags": [ + { + "tag": "TAG_NAME", + "reason": "Short reason with evidence from the transcript" + } + ], + "overall_sentiment": "positive|neutral|negative", + "call_quality_score": <1-10>, + "summary": "1-2 sentence summary of this segment" +} + +If no tags apply, return an empty tags list. Always provide sentiment, score, and summary.""" diff --git a/api/services/workflow/node_specs/end_call.py b/api/services/workflow/node_specs/end_call.py deleted file mode 100644 index 33129ed..0000000 --- a/api/services/workflow/node_specs/end_call.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Spec for the End Call node — terminal node that wraps up a conversation -and optionally extracts variables before hangup.""" - -from api.services.workflow.node_specs._base import ( - DisplayOptions, - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertyOption, - PropertySpec, - PropertyType, -) - -SPEC = NodeSpec( - name="endCall", - display_name="End Call", - description="Closes the conversation and hangs up.", - llm_hint=( - "Terminal node that politely closes the conversation. Variable " - "extraction can run before hangup. A workflow can have multiple " - "endCall nodes reached via different edge conditions." - ), - category=NodeCategory.call_node, - icon="OctagonX", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description=( - "Short identifier shown in call logs. Should describe the " - "ending context (e.g., 'Successful close', 'Polite decline')." - ), - required=True, - min_length=1, - default="End Call", - ), - PropertySpec( - name="prompt", - type=PropertyType.mention_textarea, - display_name="Prompt", - description=( - "Agent system prompt for the closing exchange. Supports " - "{{template_variables}} from extraction or pre-call fetch." - ), - required=True, - min_length=1, - placeholder="Thank the caller and confirm next steps before ending the call.", - ), - PropertySpec( - name="add_global_prompt", - type=PropertyType.boolean, - display_name="Add Global Prompt", - description=( - "When true and a Global node exists, prepends the global " - "prompt to this node's prompt at runtime." - ), - default=False, - ), - PropertySpec( - name="extraction_enabled", - type=PropertyType.boolean, - display_name="Enable Variable Extraction", - description=( - "When true, runs an LLM extraction pass before hangup to " - "capture variables from the conversation." - ), - default=False, - ), - PropertySpec( - name="extraction_prompt", - type=PropertyType.string, - display_name="Extraction Prompt", - description=( - "Overall instructions guiding how variables should be " - "extracted from the conversation." - ), - display_options=DisplayOptions(show={"extraction_enabled": [True]}), - editor="textarea", - ), - PropertySpec( - name="extraction_variables", - type=PropertyType.fixed_collection, - display_name="Variables to Extract", - description=( - "Each entry declares one variable to capture from the " - "conversation, with its name, data type, and a per-variable " - "extraction hint." - ), - display_options=DisplayOptions(show={"extraction_enabled": [True]}), - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Variable Name", - description="snake_case identifier used downstream.", - required=True, - ), - PropertySpec( - name="type", - type=PropertyType.options, - display_name="Type", - description="The data type of the extracted value.", - required=True, - default="string", - options=[ - PropertyOption(value="string", label="String"), - PropertyOption(value="number", label="Number"), - PropertyOption(value="boolean", label="Boolean"), - ], - ), - PropertySpec( - name="prompt", - type=PropertyType.string, - display_name="Extraction Hint", - description=( - "Per-variable hint describing what to look for in " - "the conversation." - ), - editor="textarea", - ), - ], - ), - ], - examples=[ - NodeExample( - name="successful_close", - data={ - "name": "Successful Close", - "prompt": "Confirm the appointment time, thank the caller, and end the call.", - "add_global_prompt": False, - }, - ), - ], - graph_constraints=GraphConstraints( - min_incoming=1, - min_outgoing=0, - max_outgoing=0, - ), -) diff --git a/api/services/workflow/node_specs/global_node.py b/api/services/workflow/node_specs/global_node.py deleted file mode 100644 index bd983d7..0000000 --- a/api/services/workflow/node_specs/global_node.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Spec for the Global node — system-level instructions appended to every -agent node that opts in via `add_global_prompt`.""" - -from api.services.workflow.node_specs._base import ( - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertySpec, - PropertyType, -) - -SPEC = NodeSpec( - name="globalNode", - display_name="Global Node", - description="Persona/tone appended to every agent node's prompt.", - llm_hint=( - "System-level prompt appended to every prompted node whose " - "`add_global_prompt` is true. Use it for persona, tone, and shared " - "rules that apply across the entire conversation. At most one " - "global node per workflow." - ), - category=NodeCategory.global_node, - icon="Globe", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description=( - "Short identifier shown in the canvas and call logs. Has no " - "runtime effect." - ), - required=True, - min_length=1, - default="Global Node", - ), - PropertySpec( - name="prompt", - type=PropertyType.mention_textarea, - display_name="Global Prompt", - description=( - "Text appended to every prompted node's system prompt when " - "that node has `add_global_prompt=true`. Supports " - "{{template_variables}}." - ), - required=True, - min_length=1, - placeholder="You are a friendly assistant calling on behalf of {{company_name}}.", - default=( - "You are a helpful assistant whose mode of interaction with " - "the user is voice. So don't use any special characters which " - "can not be pronounced. Use short sentences and simple language." - ), - ), - ], - examples=[ - NodeExample( - name="basic_persona", - description="Establishes a consistent persona across the call.", - data={ - "name": "Persona", - "prompt": ( - "You are Sarah, a polite and warm representative from " - "Acme Corp. Always thank the caller for their time and " - "speak in short conversational sentences." - ), - }, - ), - ], - graph_constraints=GraphConstraints( - min_incoming=0, - max_incoming=0, - min_outgoing=0, - max_outgoing=0, - ), -) diff --git a/api/services/workflow/node_specs/model_spec.py b/api/services/workflow/node_specs/model_spec.py new file mode 100644 index 0000000..1379ee1 --- /dev/null +++ b/api/services/workflow/node_specs/model_spec.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +from dataclasses import dataclass +from dataclasses import field as dataclass_field +from enum import Enum +from types import NoneType +from typing import Any, Callable, Literal, get_args, get_origin + +from pydantic import BaseModel, Field +from pydantic.fields import FieldInfo, PydanticUndefined + +from api.services.workflow.node_specs._base import ( + DisplayOptions, + GraphConstraints, + NodeCategory, + NodeExample, + NodeSpec, + PropertyOption, + PropertySpec, + PropertyType, +) + +_SPEC_FIELD_META_KEY = "__dograh_spec_field__" +_UNSET = object() + + +@dataclass(frozen=True) +class NodeSpecMetadata: + name: str + display_name: str + description: str + category: NodeCategory + icon: str + llm_hint: str | None = None + version: str = "1.0.0" + examples: tuple[NodeExample, ...] = () + graph_constraints: GraphConstraints | None = None + property_order: tuple[str, ...] = () + field_overrides: dict[str, dict[str, Any]] = dataclass_field(default_factory=dict) + + +def spec_field( + *field_args: Any, + ui_type: PropertyType | str | None = None, + display_name: str | None = None, + llm_hint: str | None = None, + required: bool | None = None, + spec_default: Any = _UNSET, + placeholder: str | None = None, + display_options: DisplayOptions | None = None, + options: list[PropertyOption] | None = None, + editor: str | None = None, + extra: dict[str, Any] | None = None, + spec_exclude: bool = False, + min_value: float | None = None, + max_value: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, + **field_kwargs: Any, +): + json_schema_extra = dict(field_kwargs.pop("json_schema_extra", {}) or {}) + json_schema_extra[_SPEC_FIELD_META_KEY] = { + "ui_type": ui_type.value if isinstance(ui_type, PropertyType) else ui_type, + "display_name": display_name, + "llm_hint": llm_hint, + "required": required, + "placeholder": placeholder, + "display_options": display_options, + "options": options, + "editor": editor, + "extra": extra or {}, + "spec_exclude": spec_exclude, + "min_value": min_value, + "max_value": max_value, + "min_length": min_length, + "max_length": max_length, + "pattern": pattern, + } + if spec_default is not _UNSET: + json_schema_extra[_SPEC_FIELD_META_KEY]["spec_default"] = spec_default + return Field(*field_args, json_schema_extra=json_schema_extra, **field_kwargs) + + +def node_spec( + *, + name: str, + display_name: str, + description: str, + category: NodeCategory, + icon: str, + llm_hint: str | None = None, + version: str = "1.0.0", + examples: list[NodeExample] | tuple[NodeExample, ...] = (), + graph_constraints: GraphConstraints | None = None, + property_order: list[str] | tuple[str, ...] = (), + field_overrides: dict[str, dict[str, Any]] | None = None, +) -> Callable[[type[BaseModel]], type[BaseModel]]: + metadata = NodeSpecMetadata( + name=name, + display_name=display_name, + description=description, + category=category, + icon=icon, + llm_hint=llm_hint, + version=version, + examples=tuple(examples), + graph_constraints=graph_constraints, + property_order=tuple(property_order), + field_overrides=field_overrides or {}, + ) + + def decorator(model_cls: type[BaseModel]) -> type[BaseModel]: + setattr(model_cls, "__node_spec_metadata__", metadata) + return model_cls + + return decorator + + +def build_spec(model_cls: type[BaseModel]) -> NodeSpec: + metadata: NodeSpecMetadata | None = getattr( + model_cls, "__node_spec_metadata__", None + ) + if metadata is None: + raise ValueError(f"{model_cls.__name__} is missing __node_spec_metadata__") + + properties: list[PropertySpec] = [] + for name, field in model_cls.model_fields.items(): + prop = _build_property_spec(model_cls, name, field) + if prop is not None: + properties.append(prop) + properties = _sort_properties(metadata.name, properties, metadata.property_order) + + return NodeSpec( + name=metadata.name, + display_name=metadata.display_name, + description=metadata.description, + llm_hint=metadata.llm_hint, + category=metadata.category, + icon=metadata.icon, + version=metadata.version, + properties=properties, + examples=list(metadata.examples), + graph_constraints=metadata.graph_constraints, + ) + + +def _sort_properties( + spec_name: str, + properties: list[PropertySpec], + property_order: tuple[str, ...], +) -> list[PropertySpec]: + if not property_order: + return properties + + property_names = {prop.name for prop in properties} + missing = [name for name in property_order if name not in property_names] + if missing: + raise ValueError( + f"{spec_name}: property_order references unknown properties: {missing}" + ) + + order_map = {name: idx for idx, name in enumerate(property_order)} + ordered = sorted( + enumerate(properties), + key=lambda item: (order_map.get(item[1].name, len(order_map)), item[0]), + ) + return [prop for _, prop in ordered] + + +def _build_property_spec( + owner_cls: type[BaseModel], + field_name: str, + field: FieldInfo, +) -> PropertySpec | None: + meta = _merged_field_meta(owner_cls, field_name, field) + if meta.get("spec_exclude"): + return None + + prop_type = _resolve_property_type(field.annotation, meta) + nested_properties = _resolve_nested_properties(field.annotation, prop_type) + options = _resolve_options(field.annotation, meta, prop_type) + min_value, max_value, min_length, max_length, pattern = _resolve_constraints( + field, meta + ) + + description = meta.get("description") or field.description + if not description: + raise ValueError(f"{owner_cls.__name__}.{field_name} is missing a description") + + return PropertySpec( + name=field_name, + type=prop_type, + display_name=meta.get("display_name") or _humanize_identifier(field_name), + description=description, + llm_hint=meta.get("llm_hint"), + default=_resolve_default(field, meta), + required=_resolve_required(field, meta), + placeholder=meta.get("placeholder"), + display_options=meta.get("display_options"), + options=options, + properties=nested_properties, + min_value=min_value, + max_value=max_value, + min_length=min_length, + max_length=max_length, + pattern=pattern, + editor=meta.get("editor"), + extra=meta.get("extra") or {}, + ) + + +def _merged_field_meta( + owner_cls: type[BaseModel], + field_name: str, + field: FieldInfo, +) -> dict[str, Any]: + field_meta = {} + if isinstance(field.json_schema_extra, dict): + field_meta = dict(field.json_schema_extra.get(_SPEC_FIELD_META_KEY, {}) or {}) + metadata: NodeSpecMetadata | None = getattr( + owner_cls, "__node_spec_metadata__", None + ) + override = ( + dict(metadata.field_overrides.get(field_name, {}) or {}) + if metadata is not None + else {} + ) + merged = dict(field_meta) + merged.update(override) + return merged + + +def _resolve_property_type(annotation: Any, meta: dict[str, Any]) -> PropertyType: + ui_type = meta.get("ui_type") + if ui_type: + return PropertyType(ui_type) + + inner = _strip_optional(annotation) + origin = get_origin(inner) + args = get_args(inner) + + if origin is list: + item_type = _strip_optional(args[0]) if args else Any + if isinstance(item_type, type) and issubclass(item_type, BaseModel): + return PropertyType.fixed_collection + raise ValueError( + "List-valued fields must declare an explicit ui_type unless they wrap a " + f"BaseModel row type (field annotation: {annotation!r})." + ) + + if _is_enum(inner) or _is_literal(inner): + return PropertyType.options + + if inner in (str,): + return PropertyType.string + if inner in (int, float): + return PropertyType.number + if inner is bool: + return PropertyType.boolean + if inner in (dict, Any) or origin is dict: + return PropertyType.json + + raise ValueError(f"Unable to derive PropertyType for annotation {annotation!r}") + + +def _resolve_nested_properties( + annotation: Any, + prop_type: PropertyType, +) -> list[PropertySpec] | None: + if prop_type != PropertyType.fixed_collection: + return None + + inner = _strip_optional(annotation) + args = get_args(inner) + if not args: + raise ValueError( + f"fixed_collection field annotation is missing row type: {annotation!r}" + ) + row_type = _strip_optional(args[0]) + if not isinstance(row_type, type) or not issubclass(row_type, BaseModel): + raise ValueError( + f"fixed_collection rows must be BaseModel subclasses: {annotation!r}" + ) + + properties: list[PropertySpec] = [] + for field_name, field in row_type.model_fields.items(): + prop = _build_property_spec(row_type, field_name, field) + if prop is not None: + properties.append(prop) + return properties + + +def _resolve_options( + annotation: Any, + meta: dict[str, Any], + prop_type: PropertyType, +) -> list[PropertyOption] | None: + if prop_type not in (PropertyType.options, PropertyType.multi_options): + return meta.get("options") + + if meta.get("options"): + return meta["options"] + + inner = _strip_optional(annotation) + if prop_type == PropertyType.multi_options: + inner = _strip_optional(get_args(inner)[0]) + + if _is_enum(inner): + return [ + PropertyOption( + value=member.value, label=_humanize_option_label(member.value) + ) + for member in inner + ] + if _is_literal(inner): + return [ + PropertyOption(value=value, label=_humanize_option_label(value)) + for value in get_args(inner) + if value is not None + ] + return None + + +def _resolve_constraints( + field: FieldInfo, + meta: dict[str, Any], +) -> tuple[float | None, float | None, int | None, int | None, str | None]: + min_value = meta.get("min_value") + max_value = meta.get("max_value") + min_length = meta.get("min_length") + max_length = meta.get("max_length") + pattern = meta.get("pattern") + + for item in field.metadata: + if min_value is None: + if hasattr(item, "ge") and item.ge is not None: + min_value = item.ge + elif hasattr(item, "gt") and item.gt is not None: + min_value = item.gt + if max_value is None: + if hasattr(item, "le") and item.le is not None: + max_value = item.le + elif hasattr(item, "lt") and item.lt is not None: + max_value = item.lt + if ( + min_length is None + and hasattr(item, "min_length") + and item.min_length is not None + ): + min_length = item.min_length + if ( + max_length is None + and hasattr(item, "max_length") + and item.max_length is not None + ): + max_length = item.max_length + if pattern is None and hasattr(item, "pattern") and item.pattern is not None: + pattern = item.pattern + + return min_value, max_value, min_length, max_length, pattern + + +def _resolve_default(field: FieldInfo, meta: dict[str, Any]) -> Any: + if "spec_default" in meta: + return meta["spec_default"] + if field.default is not PydanticUndefined: + return field.default + return None + + +def _resolve_required(field: FieldInfo, meta: dict[str, Any]) -> bool: + if meta.get("required") is not None: + return bool(meta["required"]) + return bool(field.is_required()) + + +def _strip_optional(annotation: Any) -> Any: + origin = get_origin(annotation) + if origin is None: + return annotation + + args = [arg for arg in get_args(annotation) if arg is not NoneType] + if len(args) == 1 and len(args) != len(get_args(annotation)): + return args[0] + return annotation + + +def _is_enum(annotation: Any) -> bool: + return isinstance(annotation, type) and issubclass(annotation, Enum) + + +def _is_literal(annotation: Any) -> bool: + return get_origin(annotation) is Literal + + +def _humanize_identifier(name: str) -> str: + return name.replace("_", " ").strip().title() + + +def _humanize_option_label(value: Any) -> str: + if isinstance(value, str): + return value.replace("_", " ").replace("-", " ").strip().title() + return str(value) diff --git a/api/services/workflow/node_specs/qa.py b/api/services/workflow/node_specs/qa.py deleted file mode 100644 index f140297..0000000 --- a/api/services/workflow/node_specs/qa.py +++ /dev/null @@ -1,203 +0,0 @@ -"""Spec for the QA Analysis node — runs an LLM quality review on the call -transcript after completion.""" - -from api.services.workflow.node_specs._base import ( - DisplayOptions, - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertyOption, - PropertySpec, - PropertyType, -) - -DEFAULT_QA_SYSTEM_PROMPT = """You are a QA analyst evaluating a specific segment of a voice AI conversation. - -## Node Purpose -{{node_summary}} - -## Previous Conversation Context (For start of conversation, previous conversation summary can be empty.) -{{previous_conversation_summary}} - -## Tags to evaluate - -Examine the conversation carefully and identify which of the following tags apply: - -- UNCLEAR_CONVERSATION - The conversation is not coherent or clear, messages don't connect logically -- ASSISTANT_IN_LOOP - The assistant asks the same question multiple times or gets stuck repeating itself -- ASSISTANT_REPLY_IMPROPER - The assistant did not reply properly to the user's question/query or seems confused by what the user said -- USER_FRUSTRATED - The user seems angry, frustrated, or is complaining about something in the call -- USER_NOT_UNDERSTANDING - The user explicitly says they don't understand or repeatedly asks for clarification -- HEARING_ISSUES - Either party can't hear the other ("hello?", "are you there?", "can you hear me?") -- DEAD_AIR - Unusually long silences in the conversation (use the timestamps to judge) -- USER_REQUESTING_FEATURE - The user asks for something the assistant can't fulfill -- ASSISTANT_LACKS_EMPATHY - The assistant ignores the user's personal situation or emotional state and continues pitching or pushing the agenda. -- USER_DETECTS_AI - The user suspects or identifies that they are talking to an AI/robot/bot rather than a real human. - -## Call metrics (pre-computed) - -Use these alongside the transcript for your analysis: -{{metrics}} - -## Output format - -Return ONLY a valid JSON object (no markdown): -{ - "tags": [ - { - "tag": "TAG_NAME", - "reason": "Short reason with evidence from the transcript" - } - ], - "overall_sentiment": "positive|neutral|negative", - "call_quality_score": <1-10>, - "summary": "1-2 sentence summary of this segment" -} - -If no tags apply, return an empty tags list. Always provide sentiment, score, and summary.""" - - -SPEC = NodeSpec( - name="qa", - display_name="QA Analysis", - description="Run LLM quality analysis on the call transcript.", - llm_hint=( - "Runs an LLM quality review on the call transcript after completion. " - "Per-node analysis splits the conversation by node and evaluates each " - "segment against the configured system prompt. Sampling, minimum " - "duration, and voicemail filters are supported." - ), - category=NodeCategory.integration, - icon="ClipboardCheck", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description="Short identifier for this QA configuration.", - required=True, - min_length=1, - default="QA Analysis", - ), - PropertySpec( - name="qa_enabled", - type=PropertyType.boolean, - display_name="Enabled", - description="When false, the QA run is skipped.", - default=True, - ), - PropertySpec( - name="qa_system_prompt", - type=PropertyType.string, - display_name="System Prompt", - description=( - "Instructions to the QA reviewer LLM. Supports placeholders: " - "`{node_summary}`, `{previous_conversation_summary}`, " - "`{transcript}`, `{metrics}`." - ), - editor="textarea", - default=DEFAULT_QA_SYSTEM_PROMPT, - ), - PropertySpec( - name="qa_min_call_duration", - type=PropertyType.number, - display_name="Minimum Call Duration (seconds)", - description="Calls shorter than this are skipped.", - default=15, - min_value=0, - ), - PropertySpec( - name="qa_voicemail_calls", - type=PropertyType.boolean, - display_name="Include Voicemail Calls", - description="When false, calls flagged as voicemail are skipped.", - default=False, - ), - PropertySpec( - name="qa_sample_rate", - type=PropertyType.number, - display_name="Sample Rate (%)", - description=( - "Percent of eligible calls QA'd. 100 means every call; lower " - "values use random sampling." - ), - default=100, - min_value=1, - max_value=100, - ), - # ---- LLM configuration ---- - PropertySpec( - name="qa_use_workflow_llm", - type=PropertyType.boolean, - display_name="Use Workflow's LLM", - description=( - "When true, the QA pass uses the same LLM the workflow runs " - "with. Set false to specify a separate provider/model." - ), - default=True, - ), - PropertySpec( - name="qa_provider", - type=PropertyType.options, - display_name="QA LLM Provider", - description="LLM provider used for the QA pass.", - display_options=DisplayOptions(show={"qa_use_workflow_llm": [False]}), - options=[ - PropertyOption(value="openai", label="OpenAI"), - PropertyOption(value="azure", label="Azure OpenAI"), - PropertyOption(value="openrouter", label="OpenRouter"), - PropertyOption(value="anthropic", label="Anthropic"), - ], - ), - PropertySpec( - name="qa_model", - type=PropertyType.string, - display_name="QA Model", - description=( - "Model identifier (e.g., 'gpt-4o', 'claude-sonnet-4-6'). " - "Provider-specific." - ), - display_options=DisplayOptions(show={"qa_use_workflow_llm": [False]}), - default="default", - ), - PropertySpec( - name="qa_api_key", - type=PropertyType.string, - display_name="API Key", - description="API key for the chosen provider.", - display_options=DisplayOptions(show={"qa_use_workflow_llm": [False]}), - ), - PropertySpec( - name="qa_endpoint", - type=PropertyType.url, - display_name="Azure Endpoint", - description="Required for the Azure provider.", - display_options=DisplayOptions( - show={"qa_use_workflow_llm": [False], "qa_provider": ["azure"]} - ), - ), - ], - examples=[ - NodeExample( - name="basic_qa", - data={ - "name": "Compliance Check", - "qa_enabled": True, - "qa_system_prompt": ( - "You are a compliance reviewer. Review the transcript and " - "produce a JSON object with `tags`, `summary`, " - "`call_quality_score`, and `overall_sentiment`." - ), - "qa_min_call_duration": 30, - "qa_sample_rate": 100, - }, - ), - ], - # QA runs post-call against the saved transcript (run_integrations - # scans by type), never as a graph step. Reject any edge into or out - # of a QA node. - graph_constraints=GraphConstraints( - min_incoming=0, max_incoming=0, min_outgoing=0, max_outgoing=0 - ), -) diff --git a/api/services/workflow/node_specs/start_call.py b/api/services/workflow/node_specs/start_call.py deleted file mode 100644 index 07cd870..0000000 --- a/api/services/workflow/node_specs/start_call.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Spec for the Start Call node — the single entry point of every workflow. -Carries greeting, pre-call data fetch, and the same prompt/extraction/tools -fields as agent nodes.""" - -from api.services.workflow.node_specs._base import ( - DisplayOptions, - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertyOption, - PropertySpec, - PropertyType, -) - -SPEC = NodeSpec( - name="startCall", - display_name="Start Call", - description="Entry point of the workflow — plays a greeting and opens the conversation.", - llm_hint=( - "The entry point of every workflow (exactly one required). Plays an " - "optional greeting, can fetch context from an external API before " - "the call begins, and executes the first conversational turn." - ), - category=NodeCategory.call_node, - icon="Play", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description="Short identifier shown in the canvas and call logs.", - required=True, - min_length=1, - default="Start Call", - ), - # ---- Greeting (variant via greeting_type) ---- - PropertySpec( - name="greeting_type", - type=PropertyType.options, - display_name="Greeting Type", - description=( - "Whether the optional greeting is spoken via TTS from text " - "or played from a pre-recorded audio file." - ), - default="text", - options=[ - PropertyOption(value="text", label="Text (TTS)"), - PropertyOption(value="audio", label="Pre-recorded Audio"), - ], - ), - PropertySpec( - name="greeting", - type=PropertyType.string, - display_name="Greeting Text", - description=( - "Text spoken via TTS at the start of the call. Supports " - "{{template_variables}}. Leave empty to skip the greeting." - ), - display_options=DisplayOptions(show={"greeting_type": ["text"]}), - editor="textarea", - placeholder="Hi {{first_name}}, this is Sarah from Acme.", - ), - PropertySpec( - name="greeting_recording_id", - type=PropertyType.recording_ref, - display_name="Greeting Recording", - description="Pre-recorded audio file played at the start of the call.", - llm_hint=( - "Value is the `recording_id` string. Use the `list_recordings` " - "MCP tool to discover available recordings." - ), - display_options=DisplayOptions(show={"greeting_type": ["audio"]}), - ), - PropertySpec( - name="prompt", - type=PropertyType.mention_textarea, - display_name="Prompt", - description=( - "Agent system prompt for the opening turn. Supports " - "{{template_variables}} from pre-call fetch and the initial context." - ), - required=True, - min_length=1, - placeholder="Greet the caller warmly and ask how you can help today.", - ), - # ---- Behavior toggles ---- - PropertySpec( - name="allow_interrupt", - type=PropertyType.boolean, - display_name="Allow Interruption", - description=("When true, the user can interrupt the agent mid-utterance."), - default=False, - ), - PropertySpec( - name="add_global_prompt", - type=PropertyType.boolean, - display_name="Add Global Prompt", - description=( - "When true and a Global node exists, prepends the global " - "prompt to this node's prompt at runtime." - ), - default=True, - ), - PropertySpec( - name="delayed_start", - type=PropertyType.boolean, - display_name="Delayed Start", - description=( - "When true, the agent waits before speaking after pickup. " - "Useful for outbound calls where the called party needs a " - "moment to settle." - ), - default=False, - ), - PropertySpec( - name="delayed_start_duration", - type=PropertyType.number, - display_name="Delay Duration (seconds)", - description="Seconds to wait before the agent speaks. 0.1–10.", - default=2.0, - min_value=0.1, - max_value=10.0, - display_options=DisplayOptions(show={"delayed_start": [True]}), - ), - # ---- Variable extraction ---- - PropertySpec( - name="extraction_enabled", - type=PropertyType.boolean, - display_name="Enable Variable Extraction", - description=( - "When true, runs an LLM extraction pass on transition out of " - "this node to capture variables from the opening turn." - ), - default=False, - ), - PropertySpec( - name="extraction_prompt", - type=PropertyType.string, - display_name="Extraction Prompt", - description="Overall instructions guiding variable extraction.", - display_options=DisplayOptions(show={"extraction_enabled": [True]}), - editor="textarea", - ), - PropertySpec( - name="extraction_variables", - type=PropertyType.fixed_collection, - display_name="Variables to Extract", - description=( - "Each entry declares one variable to capture, with its name, " - "data type, and per-variable extraction hint." - ), - display_options=DisplayOptions(show={"extraction_enabled": [True]}), - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Variable Name", - description="snake_case identifier used downstream.", - required=True, - ), - PropertySpec( - name="type", - type=PropertyType.options, - display_name="Type", - description="Data type of the extracted value.", - required=True, - default="string", - options=[ - PropertyOption(value="string", label="String"), - PropertyOption(value="number", label="Number"), - PropertyOption(value="boolean", label="Boolean"), - ], - ), - PropertySpec( - name="prompt", - type=PropertyType.string, - display_name="Extraction Hint", - description="Per-variable hint describing what to look for.", - editor="textarea", - ), - ], - ), - # ---- Tools / documents ---- - PropertySpec( - name="tool_uuids", - type=PropertyType.tool_refs, - display_name="Tools", - description="Tools the agent can invoke during the opening turn.", - llm_hint="List of tool UUIDs from `list_tools`.", - ), - PropertySpec( - name="document_uuids", - type=PropertyType.document_refs, - display_name="Knowledge Base Documents", - description="Documents the agent can reference.", - llm_hint="List of document UUIDs from `list_documents`.", - ), - # ---- Pre-call data fetch (advanced) ---- - PropertySpec( - name="pre_call_fetch_enabled", - type=PropertyType.boolean, - display_name="Pre-Call Data Fetch", - description=( - "When true, makes a POST request to an external API before " - "the call starts and merges the JSON response into the call " - "context as template variables." - ), - default=False, - ), - PropertySpec( - name="pre_call_fetch_url", - type=PropertyType.url, - display_name="Endpoint URL", - description=( - "URL the pre-call POST request is sent to. The request body " - "includes caller and called numbers." - ), - display_options=DisplayOptions(show={"pre_call_fetch_enabled": [True]}), - placeholder="https://api.example.com/customer-lookup", - ), - PropertySpec( - name="pre_call_fetch_credential_uuid", - type=PropertyType.credential_ref, - display_name="Authentication", - description="Optional credential attached to the pre-call request.", - llm_hint="Credential UUID from `list_credentials`.", - display_options=DisplayOptions(show={"pre_call_fetch_enabled": [True]}), - ), - ], - examples=[ - NodeExample( - name="warm_greeting", - data={ - "name": "Greeting", - "prompt": "Greet warmly and ask the caller's reason for calling.", - "greeting_type": "text", - "greeting": "Hi {{first_name}}, this is Sarah from Acme.", - "allow_interrupt": True, - }, - ), - ], - # `min_outgoing` is intentionally unset: a startCall is allowed to - # sit on the canvas without an outgoing edge (e.g. a workflow with - # just a greeting). Only constraint: nothing flows INTO the start. - graph_constraints=GraphConstraints( - min_incoming=0, - max_incoming=0, - ), -) diff --git a/api/services/workflow/node_specs/trigger.py b/api/services/workflow/node_specs/trigger.py deleted file mode 100644 index 5d9883d..0000000 --- a/api/services/workflow/node_specs/trigger.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Spec for the API Trigger node — exposes a public webhook URL that -external systems can hit to launch the workflow.""" - -from api.services.workflow.node_specs._base import ( - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertySpec, - PropertyType, -) - -SPEC = NodeSpec( - name="trigger", - display_name="API Trigger", - description=("Public HTTP endpoints that launch the workflow."), - llm_hint=( - "Exposes two public HTTP POST endpoints derived from the auto-generated " - "`trigger_path`:\n" - " • Production: `/api/v1/public/agent/` — runs " - "the published agent. Use this from production systems.\n" - " • Test: `/api/v1/public/agent/test/` — runs " - "the latest draft, useful for verifying changes before publishing. " - "Falls back to the published agent when no draft exists.\n" - "Both require an API key in the `X-API-Key` header.\n" - "Request body fields:\n" - " • `phone_number` (string, required) — destination to dial.\n" - " • `initial_context` (object, optional) — merged into the run's " - "initial context.\n" - " • `telephony_configuration_id` (int, optional) — pick a specific " - "telephony configuration for the call. Must belong to the same " - "organization as the trigger. When omitted, the org's default " - "outbound configuration is used." - ), - category=NodeCategory.trigger, - icon="Webhook", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description="Short identifier shown in the canvas. No runtime effect.", - required=True, - min_length=1, - default="API Trigger", - ), - PropertySpec( - name="enabled", - type=PropertyType.boolean, - display_name="Enabled", - description="When false, the trigger URL returns 404.", - default=True, - ), - PropertySpec( - name="trigger_path", - type=PropertyType.string, - display_name="Trigger Path", - description=( - "Auto-generated UUID-style path segment that uniquely " - "identifies this trigger. Used in both URLs:\n" - " • Production: `/api/v1/public/agent/` — " - "executes the published agent.\n" - " • Test: `/api/v1/public/agent/test/` — " - "executes the latest draft.\n" - "Do not edit manually." - ), - ), - ], - examples=[ - NodeExample( - name="default", - data={"name": "Inbound Trigger", "enabled": True}, - ), - ], - graph_constraints=GraphConstraints( - min_incoming=0, - max_incoming=0, - ), -) diff --git a/api/services/workflow/node_specs/webhook.py b/api/services/workflow/node_specs/webhook.py deleted file mode 100644 index 37d17f6..0000000 --- a/api/services/workflow/node_specs/webhook.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Spec for the Webhook node — sends an HTTP request to an external system -after the workflow completes.""" - -from api.services.workflow.node_specs._base import ( - GraphConstraints, - NodeCategory, - NodeExample, - NodeSpec, - PropertyOption, - PropertySpec, - PropertyType, -) - -SPEC = NodeSpec( - name="webhook", - display_name="Webhook", - description="Send HTTP request after the workflow completes.", - llm_hint=( - "Sends an HTTP request to an external system after the workflow " - "completes. The payload is a Jinja-templated JSON body with access " - "to `workflow_run_id`, `initial_context`, `gathered_context`, " - "`annotations`, and call metadata." - ), - category=NodeCategory.integration, - icon="Link2", - properties=[ - PropertySpec( - name="name", - type=PropertyType.string, - display_name="Name", - description="Short identifier shown in the canvas and run logs.", - required=True, - min_length=1, - default="Webhook", - ), - PropertySpec( - name="enabled", - type=PropertyType.boolean, - display_name="Enabled", - description="When false, the webhook is skipped at run time.", - default=True, - ), - PropertySpec( - name="http_method", - type=PropertyType.options, - display_name="HTTP Method", - description="HTTP verb used for the outbound request.", - default="POST", - options=[ - PropertyOption(value="GET", label="GET"), - PropertyOption(value="POST", label="POST"), - PropertyOption(value="PUT", label="PUT"), - PropertyOption(value="PATCH", label="PATCH"), - PropertyOption(value="DELETE", label="DELETE"), - ], - ), - PropertySpec( - name="endpoint_url", - type=PropertyType.url, - display_name="Endpoint URL", - description="URL the request is sent to.", - placeholder="https://api.example.com/webhook", - ), - PropertySpec( - name="credential_uuid", - type=PropertyType.credential_ref, - display_name="Authentication", - description="Optional credential applied as the Authorization header.", - llm_hint="Credential UUID from `list_credentials`.", - ), - PropertySpec( - name="custom_headers", - type=PropertyType.fixed_collection, - display_name="Custom Headers", - description="Additional HTTP headers to include with the request.", - properties=[ - PropertySpec( - name="key", - type=PropertyType.string, - display_name="Header Name", - description="HTTP header name (e.g., 'X-Source').", - required=True, - ), - PropertySpec( - name="value", - type=PropertyType.string, - display_name="Header Value", - description="Header value (supports {{template_variables}}).", - required=True, - ), - ], - ), - PropertySpec( - name="payload_template", - type=PropertyType.json, - display_name="Payload Template", - description=( - "JSON body of the request. Values are Jinja-rendered against " - "the run context — `{{workflow_run_id}}`, " - "`{{gathered_context.foo}}`, `{{annotations.qa_xxx}}`, etc." - ), - default={ - "call_id": "{{workflow_run_id}}", - "first_name": "{{initial_context.first_name}}", - "rsvp": "{{gathered_context.rsvp}}", - "duration": "{{cost_info.call_duration_seconds}}", - "recording_url": "{{recording_url}}", - "transcript_url": "{{transcript_url}}", - }, - ), - ], - examples=[ - NodeExample( - name="post_to_crm", - data={ - "name": "Notify CRM", - "enabled": True, - "http_method": "POST", - "endpoint_url": "https://crm.example.com/calls", - "payload_template": { - "run_id": "{{workflow_run_id}}", - "outcome": "{{gathered_context.call_disposition}}", - }, - }, - ), - ], - # Webhooks fire post-call (run_integrations scans nodes by type), - # never as a graph step. Reject any edge into or out of a webhook so - # the editor can't wire one into the conversation flow. - graph_constraints=GraphConstraints( - min_incoming=0, max_incoming=0, min_outgoing=0, max_outgoing=0 - ), -) diff --git a/api/services/workflow/pipecat_engine.py b/api/services/workflow/pipecat_engine.py index b98d4ba..b35bcfd 100644 --- a/api/services/workflow/pipecat_engine.py +++ b/api/services/workflow/pipecat_engine.py @@ -540,7 +540,7 @@ class PipecatEngine: node = self.workflow.nodes[node_id] logger.debug( - f"Executing node: name: {node.name} is_static: {node.is_static} allow_interrupt: {node.allow_interrupt} is_end: {node.is_end}" + f"Executing node: name: {node.name} allow_interrupt: {node.allow_interrupt} is_end: {node.is_end}" ) # Track previous node for transition event @@ -595,11 +595,8 @@ class PipecatEngine: ) await asyncio.sleep(delay_duration) - if node.is_static: - raise ValueError("Static nodes are not supported!") - else: - # Setup LLM Context with Prompts and Functions - await self._setup_llm_context(node) + # Setup LLM context with prompts and functions. + await self._setup_llm_context(node) def get_start_greeting(self) -> Optional[tuple[str, Optional[str]]]: """Return the greeting info for the start node, or None if not configured. @@ -626,19 +623,13 @@ class PipecatEngine: async def _handle_end_node(self, node: Node) -> None: """Handle end node execution.""" - if node.is_static: - raise ValueError("Static nodes are not supported!") - else: - # Setup LLM Context with Prompts and Functions - await self._setup_llm_context(node) + # Setup LLM context with prompts and functions. + await self._setup_llm_context(node) async def _handle_agent_node(self, node: Node) -> None: """Handle agent node execution.""" - if node.is_static: - raise ValueError("Static nodes are not supported!") - else: - # Setup LLM Context with Prompts and Functions - await self._setup_llm_context(node) + # Setup LLM context with prompts and functions. + await self._setup_llm_context(node) async def end_call_with_reason( self, diff --git a/api/services/workflow/workflow_graph.py b/api/services/workflow/workflow_graph.py index 2a2fa50..ccb8deb 100644 --- a/api/services/workflow/workflow_graph.py +++ b/api/services/workflow/workflow_graph.py @@ -1,10 +1,11 @@ import re from collections import Counter -from typing import Dict, List, Set +from typing import Any, Dict, List, Set from api.services.workflow.dto import EdgeDataDTO, NodeType, ReactFlowDTO from api.services.workflow.errors import ItemKind, WorkflowError -from api.services.workflow.node_specs import REGISTRY +from api.services.workflow.node_data import BaseNodeData +from api.services.workflow.node_specs import get_spec # Regex for matching {{ variable }} template placeholders. # Captures: group(1) = variable path, group(2) = filter name, group(3) = filter value. @@ -62,7 +63,7 @@ class Edge: class Node: - def __init__(self, id: str, node_type: NodeType, data): + def __init__(self, id: str, node_type: str, data: BaseNodeData): self.id, self.node_type, self.data = id, node_type, data self.out: Dict[str, "Node"] = {} # forward nodes self.out_edges: List[Edge] = [] # forward edges with properties @@ -75,7 +76,6 @@ class Node: # Type-specific fields — read with getattr so this works for every # node variant in the discriminated union. self.prompt = getattr(data, "prompt", None) - self.is_static = getattr(data, "is_static", False) self.allow_interrupt = getattr(data, "allow_interrupt", False) self.extraction_enabled = getattr(data, "extraction_enabled", False) self.extraction_prompt = getattr(data, "extraction_prompt", None) @@ -84,7 +84,6 @@ class Node: self.greeting = getattr(data, "greeting", None) self.greeting_type = getattr(data, "greeting_type", None) self.greeting_recording_id = getattr(data, "greeting_recording_id", None) - self.detect_voicemail = getattr(data, "detect_voicemail", False) self.delayed_start = getattr(data, "delayed_start", False) self.delayed_start_duration = getattr(data, "delayed_start_duration", None) self.tool_uuids = getattr(data, "tool_uuids", None) @@ -106,11 +105,11 @@ class WorkflowGraph: """ def __init__(self, dto: ReactFlowDTO): - # build adjacency list. n.type comes off the discriminated-union - # variant as a literal string; coerce to NodeType for downstream - # comparisons. + # Build adjacency list from validated DTO nodes. Core node comparisons + # still use NodeType string enums; integration nodes remain plain + # strings and resolve constraints through node specs. self.nodes: Dict[str, Node] = { - n.id: Node(n.id, NodeType(n.type), n.data) for n in dto.nodes + n.id: Node(n.id, n.type, n.data) for n in dto.nodes } # Store all edges @@ -140,7 +139,7 @@ class WorkflowGraph: # Get a reference to the global node try: self.global_node_id = [ - n.id for n in dto.nodes if n.type == NodeType.globalNode + n.id for n in dto.nodes if n.type == NodeType.globalNode.value ][0] except IndexError: self.global_node_id = None @@ -250,7 +249,7 @@ class WorkflowGraph: def _assert_global_node(self): errors: list[WorkflowError] = [] global_node = [ - n for n in self.nodes.values() if n.node_type == NodeType.globalNode + n for n in self.nodes.values() if n.node_type == NodeType.globalNode.value ] if not len(global_node) <= 1: errors.append( @@ -282,7 +281,7 @@ class WorkflowGraph: in_deg[m.id] += 1 for n in self.nodes.values(): - spec = REGISTRY.get(n.node_type.value) + spec = get_spec(n.node_type) if spec is None or spec.graph_constraints is None: continue gc = spec.graph_constraints diff --git a/api/tasks/campaign_tasks.py b/api/tasks/campaign_tasks.py index 6dc9ee9..286b9de 100644 --- a/api/tasks/campaign_tasks.py +++ b/api/tasks/campaign_tasks.py @@ -20,7 +20,7 @@ async def sync_campaign_source(ctx: Dict, campaign_id: int) -> None: Phase 1: Syncs data from configured source to queued_runs table - Campaign state should already be 'syncing' - Determines source type from campaign configuration - - Fetches data via appropriate sync service (Google Sheets, HubSpot, etc.) + - Fetches data via the appropriate sync service - Creates queued_run entries with unique source_uuid - Updates campaign total_rows - Transitions campaign state to 'running' on success diff --git a/api/tasks/run_integrations.py b/api/tasks/run_integrations.py index f60829c..be11a3c 100644 --- a/api/tasks/run_integrations.py +++ b/api/tasks/run_integrations.py @@ -1,7 +1,6 @@ """Execute integrations (QA analysis, webhooks) after workflow run completion.""" import random -from datetime import UTC, datetime from typing import Any, Dict, Optional import httpx @@ -14,6 +13,11 @@ from api.constants import BACKEND_API_ENDPOINT from api.db import db_client from api.db.models import WorkflowRunModel from api.enums import OrganizationConfigurationKey +from api.services.integrations import ( + IntegrationCompletionContext, + has_completion_handlers, + run_completion_handlers, +) from api.services.pipecat.tracing_config import register_org_langfuse_credentials from api.services.workflow.dto import ( QANodeData, @@ -214,16 +218,20 @@ async def run_integrations_post_workflow_run(_ctx, workflow_run_id: int): nodes = workflow_definition.get("nodes", []) qa_nodes = [n for n in nodes if n.get("type") == "qa"] webhook_nodes = [n for n in nodes if n.get("type") == "webhook"] + has_registered_integrations = has_completion_handlers(workflow_definition) - # Step 4: Generate public access token if webhooks exist or campaign_id is set + # Step 4: Generate a public access token for any run that needs post-call work. has_campaign = workflow_run.campaign_id is not None - if not webhook_nodes and not qa_nodes and not has_campaign: + if ( + not webhook_nodes + and not qa_nodes + and not has_registered_integrations + and not has_campaign + ): logger.debug("No integration nodes and no campaign, skipping") return - public_token = None - if webhook_nodes or has_campaign: - public_token = await db_client.ensure_public_access_token(workflow_run_id) + public_token = await db_client.ensure_public_access_token(workflow_run_id) # Step 5: Run QA analysis before webhooks if qa_nodes: @@ -263,17 +271,37 @@ async def run_integrations_post_workflow_run(_ctx, workflow_run_id: int): workflow_run_id ) - # Step 6: Execute webhooks + # Step 6: Run registered third-party integrations after uploads are complete + integration_results = await run_completion_handlers( + context=IntegrationCompletionContext( + workflow_run_id=workflow_run_id, + workflow_run=workflow_run, + workflow_definition=workflow_definition, + definition_id=definition_id, + organization_id=organization_id, + public_token=public_token, + ) + ) + + if integration_results: + await db_client.update_workflow_run( + workflow_run_id, annotations=integration_results + ) + workflow_run, _ = await db_client.get_workflow_run_with_context( + workflow_run_id + ) + + # Step 7: Execute webhooks if not webhook_nodes: logger.debug("No webhook nodes in workflow") return logger.info(f"Found {len(webhook_nodes)} webhook nodes to execute") - # Step 7: Build render context (includes annotations from QA) + # Step 8: Build render context (includes annotations from QA and integrations) render_context = _build_render_context(workflow_run, public_token) - # Step 8: Execute each webhook node + # Step 9: Execute each webhook node for node in webhook_nodes: node_id = node.get("id", "unknown") try: diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 2c03283..a8beed7 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -15,16 +15,14 @@ import pytest from api.services.workflow.dto import ( AgentNodeData, - AgentRFNode, EdgeDataDTO, EndCallNodeData, - EndCallRFNode, ExtractionVariableDTO, Position, ReactFlowDTO, RFEdgeDTO, + RFNodeDTO, StartCallNodeData, - StartCallRFNode, VariableType, ) from api.services.workflow.workflow_graph import WorkflowGraph @@ -270,8 +268,9 @@ def simple_workflow() -> WorkflowGraph: """ dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -290,8 +289,9 @@ def simple_workflow() -> WorkflowGraph: ], ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=200), data=EndCallNodeData( name="End Call", @@ -333,8 +333,9 @@ def three_node_workflow() -> WorkflowGraph: """ dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -353,8 +354,9 @@ def three_node_workflow() -> WorkflowGraph: ], ), ), - AgentRFNode( + RFNodeDTO( id="agent", + type="agentNode", position=Position(x=0, y=200), data=AgentNodeData( name="Collect Info", @@ -372,8 +374,9 @@ def three_node_workflow() -> WorkflowGraph: ], ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=400), data=EndCallNodeData( name="End Call", @@ -424,8 +427,9 @@ def three_node_workflow_extraction_start_only() -> WorkflowGraph: """ dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -444,8 +448,9 @@ def three_node_workflow_extraction_start_only() -> WorkflowGraph: ], ), ), - AgentRFNode( + RFNodeDTO( id="agent", + type="agentNode", position=Position(x=0, y=200), data=AgentNodeData( name="Collect Info", @@ -455,8 +460,9 @@ def three_node_workflow_extraction_start_only() -> WorkflowGraph: extraction_enabled=False, # Explicitly disabled for testing ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=400), data=EndCallNodeData( name="End Call", @@ -503,8 +509,9 @@ def three_node_workflow_no_variable_extraction() -> WorkflowGraph: """ dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -515,8 +522,9 @@ def three_node_workflow_no_variable_extraction() -> WorkflowGraph: extraction_enabled=False, ), ), - AgentRFNode( + RFNodeDTO( id="agent", + type="agentNode", position=Position(x=0, y=200), data=AgentNodeData( name="Collect Info", @@ -526,8 +534,9 @@ def three_node_workflow_no_variable_extraction() -> WorkflowGraph: extraction_enabled=False, # Explicitly disabled for testing ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=400), data=EndCallNodeData( name="End Call", diff --git a/api/tests/dto_fixtures/sample_branching_workflow.json b/api/tests/dto_fixtures/sample_branching_workflow.json index b470f71..888ae2a 100644 --- a/api/tests/dto_fixtures/sample_branching_workflow.json +++ b/api/tests/dto_fixtures/sample_branching_workflow.json @@ -63,7 +63,6 @@ }, "data": { "prompt": "Hello, I am Abhishek from Dograh. ", - "is_static": true, "name": "Start Call", "is_start": true }, @@ -83,7 +82,6 @@ }, "data": { "prompt": "Thank you for calling Dograh. Have a great day!", - "is_static": true, "name": "End Call" }, "measured": { @@ -161,4 +159,4 @@ "y": 0, "zoom": 1 } -} \ No newline at end of file +} diff --git a/api/tests/test_dograh_sdk_typed.py b/api/tests/test_dograh_sdk_typed.py index 4b23076..042f336 100644 --- a/api/tests/test_dograh_sdk_typed.py +++ b/api/tests/test_dograh_sdk_typed.py @@ -19,6 +19,7 @@ from dograh_sdk.typed import ( Qa, StartCall, Trigger, + Tuner, TypedNode, Webhook, ) @@ -50,6 +51,7 @@ def client() -> _StubClient: (Trigger, "trigger"), (Webhook, "webhook"), (Qa, "qa"), + (Tuner, "tuner"), ], ids=lambda v: v.__name__ if isinstance(v, type) else v, ) @@ -68,8 +70,15 @@ def test_typed_class_declares_spec_name(cls: type[TypedNode], expected_type: str inst = cls(name="t") elif cls is Webhook: inst = cls(name="wh") - else: # Qa + elif cls is Qa: inst = cls(name="qa") + else: # Tuner + inst = cls( + name="tuner", + tuner_agent_id="agent", + tuner_workspace_id=1, + tuner_api_key="secret", + ) assert inst.type == expected_type diff --git a/api/tests/test_dto.py b/api/tests/test_dto.py index 512252b..1f99d02 100644 --- a/api/tests/test_dto.py +++ b/api/tests/test_dto.py @@ -16,6 +16,37 @@ async def test_dto(): assert dto is not None +def test_dto_ignores_legacy_unknown_node_data_fields(): + dto = ReactFlowDTO.model_validate( + { + "nodes": [ + { + "id": "n1", + "type": "startCall", + "position": {"x": 0, "y": 0}, + "data": { + "name": "Start", + "prompt": "Hello", + "is_static": True, + "detect_voicemail": True, + "wait_for_user_response": False, + "wait_for_user_response_timeout": 2.5, + "legacy_field": "ignored", + }, + } + ], + "edges": [], + } + ) + + data = dto.nodes[0].data.model_dump() + assert "is_static" not in data + assert "detect_voicemail" not in data + assert "wait_for_user_response" not in data + assert "wait_for_user_response_timeout" not in data + assert "legacy_field" not in data + + def test_sanitize_strips_ui_runtime_fields(): definition = { "viewport": {"x": 0, "y": 0, "zoom": 1}, diff --git a/api/tests/test_node_specs.py b/api/tests/test_node_specs.py index 110927b..b4797df 100644 --- a/api/tests/test_node_specs.py +++ b/api/tests/test_node_specs.py @@ -14,7 +14,12 @@ import re import pytest -from api.services.workflow.dto import NodeType, ReactFlowDTO +from api.services.workflow.dto import ( + ReactFlowDTO, + all_node_type_names, + get_node_data_model, +) +from api.services.workflow.node_data import BaseNodeData from api.services.workflow.node_specs import ( NodeSpec, PropertySpec, @@ -118,9 +123,9 @@ def test_fixed_collection_has_sub_properties(spec: NodeSpec): @pytest.mark.parametrize("spec", all_specs(), ids=lambda s: s.name) def test_spec_name_matches_dto_discriminator(spec: NodeSpec): - valid_names = {t.value for t in NodeType} + valid_names = all_node_type_names() assert spec.name in valid_names, ( - f"NodeSpec {spec.name!r} doesn't match any NodeType discriminator. " + f"NodeSpec {spec.name!r} doesn't match any registered node type. " f"Valid: {sorted(valid_names)}" ) @@ -187,10 +192,226 @@ def test_examples_validate_against_dto(spec: NodeSpec): def test_all_dto_types_have_specs(): - """Every NodeType discriminator value must have a registered NodeSpec — - catches the case where someone adds a new node type to dto.py but - forgets to author a spec.""" + """Every registered node type must have a registered NodeSpec.""" spec_names = {s.name for s in all_specs()} - type_values = {t.value for t in NodeType} + type_values = all_node_type_names() missing = type_values - spec_names - assert not missing, f"NodeType discriminators without specs: {sorted(missing)}" + assert not missing, f"Registered node types without specs: {sorted(missing)}" + + +def test_all_registered_node_models_inherit_base_node_data(): + for type_name in sorted(all_node_type_names()): + data_model = get_node_data_model(type_name) + assert data_model is not None, f"{type_name}: missing node data model" + assert issubclass(data_model, BaseNodeData), ( + f"{type_name}: node data model must inherit BaseNodeData" + ) + + +@pytest.mark.parametrize( + ("spec_name", "expected_order"), + [ + ( + "startCall", + [ + "name", + "greeting_type", + "greeting", + "greeting_recording_id", + "prompt", + "allow_interrupt", + "add_global_prompt", + "delayed_start", + "delayed_start_duration", + "extraction_enabled", + "extraction_prompt", + "extraction_variables", + "tool_uuids", + "document_uuids", + "pre_call_fetch_enabled", + "pre_call_fetch_url", + "pre_call_fetch_credential_uuid", + ], + ), + ( + "agentNode", + [ + "name", + "prompt", + "allow_interrupt", + "add_global_prompt", + "extraction_enabled", + "extraction_prompt", + "extraction_variables", + "tool_uuids", + "document_uuids", + ], + ), + ( + "endCall", + [ + "name", + "prompt", + "add_global_prompt", + "extraction_enabled", + "extraction_prompt", + "extraction_variables", + ], + ), + ("globalNode", ["name", "prompt"]), + ("trigger", ["name", "enabled", "trigger_path"]), + ( + "webhook", + [ + "name", + "enabled", + "http_method", + "endpoint_url", + "credential_uuid", + "custom_headers", + "payload_template", + ], + ), + ( + "qa", + [ + "name", + "qa_enabled", + "qa_system_prompt", + "qa_min_call_duration", + "qa_voicemail_calls", + "qa_sample_rate", + "qa_use_workflow_llm", + "qa_provider", + "qa_model", + "qa_api_key", + "qa_endpoint", + ], + ), + ( + "tuner", + [ + "name", + "tuner_enabled", + "tuner_agent_id", + "tuner_workspace_id", + "tuner_api_key", + ], + ), + ], +) +def test_node_spec_property_order_stable(spec_name: str, expected_order: list[str]): + spec = next(spec for spec in all_specs() if spec.name == spec_name) + assert [prop.name for prop in spec.properties] == expected_order + + +# ───────────────────────────────────────────────────────────────────────── +# `to_mcp_dict` projection — the lean view served by the `get_node_type` +# MCP tool. UI-only metadata is dropped so it doesn't poison LLM context; +# the full spec stays available to the frontend and SDK via other paths. +# ───────────────────────────────────────────────────────────────────────── + +# Keys that are UI-rendering concerns and must never reach the LLM view, at +# either the node or property level. +_UI_ONLY_KEYS = frozenset( + { + "display_name", + "icon", + "category", + "version", + "placeholder", + "display_options", + "editor", + "extra", + "label", # PropertyOption display string + } +) + + +def _walk_dicts(node): + """Yield every dict nested anywhere inside a projected structure.""" + if isinstance(node, dict): + yield node + for value in node.values(): + yield from _walk_dicts(value) + elif isinstance(node, list): + for item in node: + yield from _walk_dicts(item) + + +@pytest.mark.parametrize("spec", all_specs(), ids=lambda s: s.name) +def test_to_mcp_dict_drops_ui_only_keys(spec: NodeSpec): + projected = spec.to_mcp_dict() + for d in _walk_dicts(projected): + leaked = _UI_ONLY_KEYS & d.keys() + assert not leaked, f"{spec.name}: UI-only keys leaked into LLM view: {leaked}" + + +@pytest.mark.parametrize("spec", all_specs(), ids=lambda s: s.name) +def test_to_mcp_dict_omits_null_and_empty(spec: NodeSpec): + """The lean view never emits null values — absent means unset/optional, + which is what halves the noise versus the full model dump.""" + for d in _walk_dicts(spec.to_mcp_dict()): + for key, value in d.items(): + assert value is not None, f"{spec.name}: {key!r} emitted as null" + + +@pytest.mark.parametrize("spec", all_specs(), ids=lambda s: s.name) +def test_to_mcp_dict_keeps_property_essentials(spec: NodeSpec): + """Every property in the LLM view carries the minimum an LLM needs to + author a value: machine name, type, and a description.""" + + def _check(props: list[dict]): + for prop in props: + assert prop.get("name"), f"{spec.name}: property missing name" + assert prop.get("type"), f"{spec.name}.{prop.get('name')}: missing type" + assert prop.get("description"), ( + f"{spec.name}.{prop.get('name')}: missing description" + ) + if prop.get("properties"): + _check(prop["properties"]) + + _check(spec.to_mcp_dict()["properties"]) + + +def test_to_mcp_dict_retains_authoring_signal_startcall(): + """startCall is the richest core node — lock in that the projection + keeps the fields an LLM actually authors against while shedding the rest.""" + spec = next(s for s in all_specs() if s.name == "startCall") + projected = spec.to_mcp_dict() + + assert set(projected) == { + "name", + "description", + "llm_hint", + "properties", + "examples", + "graph_constraints", + } + + props = {p["name"]: p for p in projected["properties"]} + + # Required field keeps `required`; optional fields omit it. + assert props["prompt"]["required"] is True + assert "required" not in props["greeting"] + + # Enum options project to bare values, dropping the UI label. + assert props["greeting_type"]["options"] == [{"value": "text"}, {"value": "audio"}] + + # Validation bounds survive (they constrain valid authored values). + assert props["delayed_start_duration"]["min_value"] == 0.1 + assert props["delayed_start_duration"]["max_value"] == 10.0 + + # llm_hint survives where present (catalog-tool references). + assert "list_recordings" in props["greeting_recording_id"]["llm_hint"] + + # fixed_collection rows recurse through the same projection. + var_rows = {p["name"]: p for p in props["extraction_variables"]["properties"]} + assert var_rows["type"]["options"] == [ + {"value": "string"}, + {"value": "number"}, + {"value": "boolean"}, + ] + + # graph_constraints drops its null sub-fields. + assert projected["graph_constraints"] == {"min_incoming": 0, "max_incoming": 0} diff --git a/api/tests/test_pipecat_engine_end_call.py b/api/tests/test_pipecat_engine_end_call.py index 2ba4570..523ad54 100644 --- a/api/tests/test_pipecat_engine_end_call.py +++ b/api/tests/test_pipecat_engine_end_call.py @@ -45,12 +45,11 @@ from api.enums import ToolCategory from api.services.workflow.dto import ( EdgeDataDTO, EndCallNodeData, - EndCallRFNode, Position, ReactFlowDTO, RFEdgeDTO, + RFNodeDTO, StartCallNodeData, - StartCallRFNode, ) from api.services.workflow.pipecat_engine import PipecatEngine from api.services.workflow.pipecat_engine_custom_tools import CustomToolManager @@ -1014,8 +1013,9 @@ class TestEndCallExtractionBehavior: # Create a workflow where start node has NO extraction dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -1026,8 +1026,9 @@ class TestEndCallExtractionBehavior: extraction_enabled=False, # No extraction ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=200), data=EndCallNodeData( name="End Call", diff --git a/api/tests/test_text_and_audio_playback.py b/api/tests/test_text_and_audio_playback.py index 39b77aa..a0fb663 100644 --- a/api/tests/test_text_and_audio_playback.py +++ b/api/tests/test_text_and_audio_playback.py @@ -34,12 +34,11 @@ from api.services.pipecat.recording_audio_cache import RecordingAudio from api.services.workflow.dto import ( EdgeDataDTO, EndCallNodeData, - EndCallRFNode, Position, ReactFlowDTO, RFEdgeDTO, + RFNodeDTO, StartCallNodeData, - StartCallRFNode, ) from api.services.workflow.pipecat_engine import PipecatEngine from api.services.workflow.pipecat_engine_custom_tools import CustomToolManager @@ -65,8 +64,9 @@ def text_workflow() -> WorkflowGraph: """Start->End workflow with text greeting and text transition speech.""" dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -79,8 +79,9 @@ def text_workflow() -> WorkflowGraph: extraction_enabled=False, ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=200), data=EndCallNodeData( name="End Call", @@ -114,8 +115,9 @@ def audio_workflow() -> WorkflowGraph: """Start->End workflow with audio greeting and audio transition speech.""" dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start Call", @@ -128,8 +130,9 @@ def audio_workflow() -> WorkflowGraph: extraction_enabled=False, ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=200), data=EndCallNodeData( name="End Call", @@ -290,8 +293,9 @@ class TestStartGreeting: """No greeting configured should return None.""" dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start", @@ -301,8 +305,9 @@ class TestStartGreeting: extraction_enabled=False, ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=200), data=EndCallNodeData( name="End", @@ -333,8 +338,9 @@ class TestStartGreeting: """Text greeting with {{variable}} placeholders should be rendered.""" dto = ReactFlowDTO( nodes=[ - StartCallRFNode( + RFNodeDTO( id="start", + type="startCall", position=Position(x=0, y=0), data=StartCallNodeData( name="Start", @@ -346,8 +352,9 @@ class TestStartGreeting: extraction_enabled=False, ), ), - EndCallRFNode( + RFNodeDTO( id="end", + type="endCall", position=Position(x=0, y=200), data=EndCallNodeData( name="End", diff --git a/api/tests/test_workflow_qa_masking.py b/api/tests/test_workflow_qa_masking.py index 1377ab7..e1a1ac7 100644 --- a/api/tests/test_workflow_qa_masking.py +++ b/api/tests/test_workflow_qa_masking.py @@ -18,6 +18,25 @@ def _qa_node(node_id="qa-1", api_key="", **extra_data): return {"id": node_id, "type": "qa", "position": {"x": 0, "y": 0}, "data": data} +def _tuner_node(node_id="tuner-1", api_key="", **extra_data): + """Helper to build a Tuner node.""" + data = { + "name": "Tuner", + "tuner_enabled": True, + "tuner_agent_id": "sales-bot", + "tuner_workspace_id": 7, + **extra_data, + } + if api_key: + data["tuner_api_key"] = api_key + return { + "id": node_id, + "type": "tuner", + "position": {"x": 0, "y": 0}, + "data": data, + } + + def _agent_node(node_id="agent-1"): """Helper to build a non-QA node.""" return { @@ -66,6 +85,19 @@ class TestMaskWorkflowDefinition: assert "qa_api_key" not in masked["nodes"][0]["data"] assert masked["nodes"][1]["data"]["qa_api_key"] == mask_key("sk-secret1234") + def test_masks_tuner_api_key(self): + """Tuner node api_key is masked, showing only last 4 chars.""" + real_key = "tuner_live_abcdefghijklmnop" + wf = _make_workflow_def([_tuner_node(api_key=real_key)]) + + masked = mask_workflow_definition(wf) + + masked_key = masked["nodes"][0]["data"]["tuner_api_key"] + assert masked_key == mask_key(real_key) + assert masked_key.endswith("mnop") + assert masked_key.startswith("*") + assert real_key not in str(masked) + def test_qa_node_without_api_key(self): """QA node with no api_key is left as-is.""" wf = _make_workflow_def([_qa_node()]) @@ -154,6 +186,16 @@ class TestMergeWorkflowApiKeys: assert result["nodes"][0]["data"]["qa_api_key"] == new_key + def test_masked_tuner_key_is_restored(self): + """Masked Tuner keys round-trip without losing the stored secret.""" + real_key = "tuner_live_abcdefghijklmnop" + existing = _make_workflow_def([_tuner_node(api_key=real_key)]) + incoming = _make_workflow_def([_tuner_node(api_key=mask_key(real_key))]) + + result = merge_workflow_api_keys(incoming, existing) + + assert result["nodes"][0]["data"]["tuner_api_key"] == real_key + def test_no_incoming_api_key(self): """QA node without api_key in incoming is left alone.""" existing = _make_workflow_def([_qa_node(api_key="sk-existing-key1")]) diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json index c37401e..762a8a1 100644 --- a/docs/api-reference/openapi.json +++ b/docs/api-reference/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"Dograh API","description":"API for the Dograh app","version":"1.0.0"},"servers":[{"url":"https://app.dograh.com","description":"Production"},{"url":"http://localhost:8000","description":"Local development"}],"paths":{"/api/v1/telephony/initiate-call":{"post":{"tags":["main"],"summary":"Initiate Call","description":"Initiate a call using the configured telephony provider from web browser. This is\nsupposed to be a test call method for the draft version of the agent.","operationId":"initiate_call_api_v1_telephony_initiate_call_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiateCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"test_phone_call","x-sdk-description":"Place a test call from a workflow to a phone number."}},"/api/v1/telephony/inbound/run":{"post":{"tags":["main"],"summary":"Handle Inbound Run","description":"Workflow-agnostic inbound dispatcher.\n\nAll providers can point a single webhook at this endpoint instead of one\nURL per workflow. The dispatcher resolves the org from the webhook's\naccount_id and the workflow from the called number's\n``inbound_workflow_id``. This is what ``configure_inbound`` writes into\neach provider's resource so per-workflow webhook bookkeeping disappears.\n\nProvider-specific signature/timestamp headers are not enumerated here \u2014\neach provider's ``verify_inbound_signature`` reads its own headers from\nthe dict, so adding a new provider doesn't require changes to this route.","operationId":"handle_inbound_run_api_v1_telephony_inbound_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/telephony/inbound/fallback":{"post":{"tags":["main"],"summary":"Handle Inbound Fallback","description":"Fallback endpoint that returns audio message when calls cannot be processed.","operationId":"handle_inbound_fallback_api_v1_telephony_inbound_fallback_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/telephony/inbound/{workflow_id}":{"post":{"tags":["main"],"summary":"Handle Inbound Telephony","description":"[LEGACY] Per-workflow inbound webhook.\n\nSuperseded by ``POST /inbound/run``, which resolves the workflow from\nthe called number's ``inbound_workflow_id`` and lets a single webhook\nURL serve every workflow in the org. New integrations should point\ntheir provider at ``/inbound/run``; this route is kept only for\nexisting provider configurations that still encode ``workflow_id``\nin the URL.","operationId":"handle_inbound_telephony_api_v1_telephony_inbound__workflow_id__post","deprecated":true,"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/transfer-result/{transfer_id}":{"post":{"tags":["main"],"summary":"Complete Transfer Function Call","description":"Webhook endpoint to complete the function call with transfer result.\n\nCalled by Twilio's StatusCallback when the transfer call status changes.","operationId":"complete_transfer_function_call_api_v1_telephony_transfer_result__transfer_id__post","parameters":[{"name":"transfer_id","in":"path","required":true,"schema":{"type":"string","title":"Transfer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/cloudonix/status-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Cloudonix Status Callback","description":"Handle Cloudonix-specific status callbacks.\n\nCloudonix sends call status updates to the callback URL specified during call initiation.","operationId":"handle_cloudonix_status_callback_api_v1_telephony_cloudonix_status_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/cloudonix/cdr":{"post":{"tags":["main"],"summary":"Handle Cloudonix Cdr","description":"Handle Cloudonix CDR (Call Detail Record) webhooks.\n\nCloudonix sends CDR records when calls complete. The CDR contains:\n- domain: Used to identify the organization\n- call_id: Used to find the workflow run\n- disposition: Call termination status (ANSWER, BUSY, CANCEL, FAILED, CONGESTION, NOANSWER)\n- duration/billsec: Call duration information","operationId":"handle_cloudonix_cdr_api_v1_telephony_cloudonix_cdr_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/telephony/plivo/hangup-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Plivo Hangup Callback","description":"Handle Plivo hangup callbacks.","operationId":"handle_plivo_hangup_callback_api_v1_telephony_plivo_hangup_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-plivo-signature-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3"}},{"name":"x-plivo-signature-ma-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-Ma-V3"}},{"name":"x-plivo-signature-v3-nonce","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3-Nonce"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/plivo/ring-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Plivo Ring Callback","description":"Handle Plivo ring callbacks.","operationId":"handle_plivo_ring_callback_api_v1_telephony_plivo_ring_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-plivo-signature-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3"}},{"name":"x-plivo-signature-ma-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-Ma-V3"}},{"name":"x-plivo-signature-v3-nonce","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3-Nonce"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/telnyx/events/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Telnyx Events","description":"Handle Telnyx Call Control webhook events.\n\nTelnyx sends all call lifecycle events (call.initiated, call.answered,\ncall.hangup, streaming.started, streaming.stopped) as JSON POST requests.","operationId":"handle_telnyx_events_api_v1_telephony_telnyx_events__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/telnyx/transfer-result/{transfer_id}":{"post":{"tags":["main"],"summary":"Handle Telnyx Transfer Result","description":"Handle Telnyx Call Control events for the transfer destination leg.\n\nThe destination leg is dialed by :meth:`TelnyxProvider.transfer_call` with\nthis URL as ``webhook_url``. Telnyx sends every event for that leg here.\nOutcomes:\n\n- ``call.answered``: seed a conference with the destination's live\n ``call_control_id``, stamp ``conference_id`` onto the TransferContext,\n and publish ``DESTINATION_ANSWERED`` so ``transfer_call_handler`` can\n end the pipeline. ``TelnyxConferenceStrategy`` then joins the caller\n into this conference at pipeline teardown.\n- ``call.hangup`` pre-answer (no ``conference_id`` on the context):\n publish ``TRANSFER_FAILED`` so the LLM can recover.\n- ``call.hangup`` post-answer (``conference_id`` set): the destination\n left a bridged conference; hang up the caller's leg to tear down the\n empty bridge (Telnyx's create_conference doesn't accept\n ``end_conference_on_exit`` on the seed leg).\n\nEvent references:\n - call.answered: https://developers.telnyx.com/api-reference/callbacks/call-answered\n - call.hangup: https://developers.telnyx.com/api-reference/callbacks/call-hangup","operationId":"handle_telnyx_transfer_result_api_v1_telephony_telnyx_transfer_result__transfer_id__post","parameters":[{"name":"transfer_id","in":"path","required":true,"schema":{"type":"string","title":"Transfer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/twilio/status-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Twilio Status Callback","description":"Handle Twilio-specific status callbacks.","operationId":"handle_twilio_status_callback_api_v1_telephony_twilio_status_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-webhook-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Webhook-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vobiz/hangup-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Vobiz Hangup Callback","description":"Handle Vobiz hangup callback (sent when call ends).\n\nVobiz sends callbacks to hangup_url when the call terminates.\nThis includes call duration, status, and billing information.","operationId":"handle_vobiz_hangup_callback_api_v1_telephony_vobiz_hangup_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-vobiz-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Signature"}},{"name":"x-vobiz-timestamp","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Timestamp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vobiz/ring-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Vobiz Ring Callback","description":"Handle Vobiz ring callback (sent when call starts ringing).\n\nVobiz can send callbacks to ring_url when the call starts ringing.\nThis is optional and used for tracking ringing status.","operationId":"handle_vobiz_ring_callback_api_v1_telephony_vobiz_ring_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-vobiz-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Signature"}},{"name":"x-vobiz-timestamp","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Timestamp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vobiz/hangup-callback/workflow/{workflow_id}":{"post":{"tags":["main"],"summary":"Handle Vobiz Hangup Callback By Workflow","description":"Handle Vobiz hangup callback with workflow_id - finds workflow run by call_id.","operationId":"handle_vobiz_hangup_callback_by_workflow_api_v1_telephony_vobiz_hangup_callback_workflow__workflow_id__post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"x-vobiz-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Signature"}},{"name":"x-vobiz-timestamp","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Timestamp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vonage/events/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Vonage Events","description":"Handle Vonage-specific event webhooks.\n\nVonage sends all call events to a single endpoint.\nEvents include: started, ringing, answered, complete, failed, etc.","operationId":"handle_vonage_events_api_v1_telephony_vonage_events__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/superuser/impersonate":{"post":{"tags":["main","superuser"],"summary":"Impersonate","description":"Impersonate a user as a super-admin.\nInternally, Stack Auth requires the **provider user ID** (a UUID-ish string)\nto create an impersonation session.","operationId":"impersonate_api_v1_superuser_impersonate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpersonateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpersonateResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/superuser/workflow-runs":{"get":{"tags":["main","superuser"],"summary":"Get Workflow Runs","description":"Get paginated list of all workflow runs with organization information.\nRequires superuser privileges.\n\nFilters should be provided as a JSON-encoded array of filter criteria.\nExample: [{\"field\": \"id\", \"type\": \"number\", \"value\": {\"value\": 680}}]","operationId":"get_workflow_runs_api_v1_superuser_workflow_runs_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number (starts from 1)","default":1,"title":"Page"},"description":"Page number (starts from 1)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of items per page","default":50,"title":"Limit"},"description":"Number of items per page"},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded filter criteria","title":"Filters"},"description":"JSON-encoded filter criteria"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Field to sort by (e.g., 'duration', 'created_at')","title":"Sort By"},"description":"Field to sort by (e.g., 'duration', 'created_at')"},{"name":"sort_order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort order ('asc' or 'desc')","default":"desc","title":"Sort Order"},"description":"Sort order ('asc' or 'desc')"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperuserWorkflowRunsListResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/validate":{"post":{"tags":["main"],"summary":"Validate Workflow","description":"Validate all nodes in a workflow to ensure they have required fields.\n\nArgs:\n workflow_id: The ID of the workflow to validate\n user: The authenticated user\n\nReturns:\n Object indicating if workflow is valid and any invalid nodes/edges","operationId":"validate_workflow_api_v1_workflow__workflow_id__validate_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateWorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/create/definition":{"post":{"tags":["main"],"summary":"Create Workflow","description":"Create a new workflow from the client\n\nArgs:\n request: The create workflow request\n user: The user to create the workflow for","operationId":"create_workflow_api_v1_workflow_create_definition_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"create_workflow","x-sdk-description":"Create a new workflow from a workflow definition."}},"/api/v1/workflow/create/template":{"post":{"tags":["main"],"summary":"Create Workflow From Template","description":"Create a new workflow from a natural language template request.\n\nThis endpoint:\n1. Uses mps_service_key_client to call MPS workflow API\n2. Passes organization ID (authenticated mode) or created_by (OSS mode)\n3. Creates the workflow in the database\n\nArgs:\n request: The template creation request with call_type, use_case, and activity_description\n user: The authenticated user\n\nReturns:\n The created workflow\n\nRaises:\n HTTPException: If MPS API call fails","operationId":"create_workflow_from_template_api_v1_workflow_create_template_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/count":{"get":{"tags":["main"],"summary":"Get Workflow Count","description":"Get workflow counts for the authenticated user's organization.\n\nThis is a lightweight endpoint for checking if the user has workflows,\nuseful for redirect logic without fetching full workflow data.","operationId":"get_workflow_count_api_v1_workflow_count_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowCountResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/fetch":{"get":{"tags":["main"],"summary":"Get Workflows","description":"Get all workflows for the authenticated user's organization.\n\nReturns a lightweight response with only essential fields for listing.\nUse GET /workflow/fetch/{workflow_id} to get full workflow details.","operationId":"get_workflows_api_v1_workflow_fetch_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status - can be single value (active/archived) or comma-separated (active,archived)","title":"Status"},"description":"Filter by status - can be single value (active/archived) or comma-separated (active,archived)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowListResponse"},"title":"Response Get Workflows Api V1 Workflow Fetch Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_workflows","x-sdk-description":"List all workflows in the authenticated organization."}},"/api/v1/workflow/fetch/{workflow_id}":{"get":{"tags":["main"],"summary":"Get Workflow","description":"Get a single workflow by ID.\n\nIf a draft version exists, returns the draft content for editing.\nOtherwise returns the published version's content.","operationId":"get_workflow_api_v1_workflow_fetch__workflow_id__get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"get_workflow","x-sdk-description":"Get a single workflow by ID (returns draft if one exists, else published)."}},"/api/v1/workflow/{workflow_id}/versions":{"get":{"tags":["main"],"summary":"Get Workflow Versions","description":"List versions for a workflow, newest first.\n\nPass `limit`/`offset` to page through long histories. With no `limit`,\nreturns every version (legacy behavior).","operationId":"get_workflow_versions_api_v1_workflow__workflow_id__versions_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowVersionResponse"},"title":"Response Get Workflow Versions Api V1 Workflow Workflow Id Versions Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/publish":{"post":{"tags":["main"],"summary":"Publish Workflow","description":"Publish the current draft version of a workflow.\n\nDrafts are allowed to be incomplete (so the editor can save mid-edit),\nbut a published version is what runtime executes \u2014 so this is the gate\nwhere the full DTO + graph + trigger-conflict checks must pass.","operationId":"publish_workflow_api_v1_workflow__workflow_id__publish_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/create-draft":{"post":{"tags":["main"],"summary":"Create Workflow Draft","description":"Create a draft version from the current published version.\n\nIf a draft already exists, returns the existing draft.","operationId":"create_workflow_draft_api_v1_workflow__workflow_id__create_draft_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowVersionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/summary":{"get":{"tags":["main"],"summary":"Get Workflows Summary","description":"Get minimal workflow information (id and name only) for all workflows","operationId":"get_workflows_summary_api_v1_workflow_summary_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status (e.g. 'active' or 'archived'). Omit to return all.","title":"Status"},"description":"Filter by status (e.g. 'active' or 'archived'). Omit to return all."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSummaryResponse"},"title":"Response Get Workflows Summary Api V1 Workflow Summary Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/status":{"put":{"tags":["main"],"summary":"Update Workflow Status","description":"Update the status of a workflow (e.g., archive/unarchive).\n\nArgs:\n workflow_id: The ID of the workflow to update\n request: The status update request\n\nReturns:\n The updated workflow","operationId":"update_workflow_status_api_v1_workflow__workflow_id__status_put","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkflowStatusRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}":{"put":{"tags":["main"],"summary":"Update Workflow","description":"Update an existing workflow.\n\nArgs:\n workflow_id: The ID of the workflow to update\n request: The update request containing the new name and workflow definition\n\nReturns:\n The updated workflow\n\nRaises:\n HTTPException: If the workflow is not found or if there's a database error","operationId":"update_workflow_api_v1_workflow__workflow_id__put","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkflowRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"update_workflow","x-sdk-description":"Update a workflow's name and/or definition. Saves as a new draft."}},"/api/v1/workflow/{workflow_id}/duplicate":{"post":{"tags":["main"],"summary":"Duplicate Workflow Endpoint","description":"Duplicate a workflow including its definition, configuration, recordings, and triggers.","operationId":"duplicate_workflow_endpoint_api_v1_workflow__workflow_id__duplicate_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/runs":{"post":{"tags":["main"],"summary":"Create Workflow Run","description":"Create a new workflow run when the user decides to execute the workflow via chat or voice\n\nArgs:\n workflow_id: The ID of the workflow to run\n request: The create workflow run request\n user: The user to create the workflow run for","operationId":"create_workflow_run_api_v1_workflow__workflow_id__runs_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRunResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["main"],"summary":"Get Workflow Runs","description":"Get workflow runs with optional filtering and sorting.\n\nFilters should be provided as a JSON-encoded array of filter criteria.\nExample: [{\"attribute\": \"dateRange\", \"value\": {\"from\": \"2024-01-01\", \"to\": \"2024-01-31\"}}]","operationId":"get_workflow_runs_api_v1_workflow__workflow_id__runs_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded filter criteria","title":"Filters"},"description":"JSON-encoded filter criteria"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Field to sort by (e.g., 'duration', 'created_at')","title":"Sort By"},"description":"Field to sort by (e.g., 'duration', 'created_at')"},{"name":"sort_order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort order ('asc' or 'desc')","default":"desc","title":"Sort Order"},"description":"Sort order ('asc' or 'desc')"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/runs/{run_id}":{"get":{"tags":["main"],"summary":"Get Workflow Run","operationId":"get_workflow_run_api_v1_workflow__workflow_id__runs__run_id__get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"integer","title":"Run Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/report":{"get":{"tags":["main"],"summary":"Download Workflow Report","description":"Download a CSV report of completed runs for a workflow.","operationId":"download_workflow_report_api_v1_workflow__workflow_id__report_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or after this datetime (ISO 8601)","title":"Start Date"},"description":"Filter runs created on or after this datetime (ISO 8601)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or before this datetime (ISO 8601)","title":"End Date"},"description":"Filter runs created on or before this datetime (ISO 8601)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/templates":{"get":{"tags":["main"],"summary":"Get Workflow Templates","description":"Get all available workflow templates.\n\nReturns:\n List of workflow templates","operationId":"get_workflow_templates_api_v1_workflow_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WorkflowTemplateResponse"},"type":"array","title":"Response Get Workflow Templates Api V1 Workflow Templates Get"}}}},"404":{"description":"Not found"}}}},"/api/v1/workflow/templates/duplicate":{"post":{"tags":["main"],"summary":"Duplicate Workflow Template","description":"Duplicate a workflow template to create a new workflow for the user.\n\nArgs:\n request: The duplicate template request\n user: The authenticated user\n\nReturns:\n The newly created workflow","operationId":"duplicate_workflow_template_api_v1_workflow_templates_duplicate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/ambient-noise/upload-url":{"post":{"tags":["main"],"summary":"Get a presigned URL to upload a custom ambient noise audio file","description":"Generate a presigned PUT URL for uploading a custom ambient noise file.","operationId":"get_ambient_noise_upload_url_api_v1_workflow_ambient_noise_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AmbientNoiseUploadRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AmbientNoiseUploadResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/defaults":{"get":{"tags":["main"],"summary":"Get Default Configurations","operationId":"get_default_configurations_api_v1_user_configurations_defaults_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DefaultConfigurationsResponse"}}}},"404":{"description":"Not found"}}}},"/api/v1/user/auth/user":{"get":{"tags":["main"],"summary":"Get Auth User","operationId":"get_auth_user_api_v1_user_auth_user_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/user":{"get":{"tags":["main"],"summary":"Get User Configurations","operationId":"get_user_configurations_api_v1_user_configurations_user_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConfigurationRequestResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update User Configurations","operationId":"update_user_configurations_api_v1_user_configurations_user_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConfigurationRequestResponseSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConfigurationRequestResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/user/validate":{"get":{"tags":["main"],"summary":"Validate User Configurations","operationId":"validate_user_configurations_api_v1_user_configurations_user_validate_get","parameters":[{"name":"validity_ttl_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":86400,"minimum":0,"default":60,"title":"Validity Ttl Seconds"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyStatusResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/api-keys":{"get":{"tags":["main"],"summary":"Get Api Keys","description":"Get all API keys for the user's selected organization.","operationId":"get_api_keys_api_v1_user_api_keys_get","parameters":[{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Archived"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/APIKeyResponse"},"title":"Response Get Api Keys Api V1 User Api Keys Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main"],"summary":"Create Api Key","description":"Create a new API key for the user's selected organization.","operationId":"create_api_key_api_v1_user_api_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/api-keys/{api_key_id}":{"delete":{"tags":["main"],"summary":"Archive Api Key","description":"Archive an API key (soft delete).","operationId":"archive_api_key_api_v1_user_api_keys__api_key_id__delete","parameters":[{"name":"api_key_id","in":"path","required":true,"schema":{"type":"integer","title":"Api Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Archive Api Key Api V1 User Api Keys Api Key Id Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/api-keys/{api_key_id}/reactivate":{"put":{"tags":["main"],"summary":"Reactivate Api Key","description":"Reactivate an archived API key.","operationId":"reactivate_api_key_api_v1_user_api_keys__api_key_id__reactivate_put","parameters":[{"name":"api_key_id","in":"path","required":true,"schema":{"type":"integer","title":"Api Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reactivate Api Key Api V1 User Api Keys Api Key Id Reactivate Put"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/voices/{provider}":{"get":{"tags":["main"],"summary":"Get Voices","description":"Get available voices for a TTS provider.","operationId":"get_voices_api_v1_user_configurations_voices__provider__get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"enum":["elevenlabs","deepgram","sarvam","cartesia","dograh","rime"],"type":"string","title":"Provider"}},{"name":"model","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"}},{"name":"language","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoicesResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/create":{"post":{"tags":["main"],"summary":"Create Campaign","description":"Create a new campaign","operationId":"create_campaign_api_v1_campaign_create_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/":{"get":{"tags":["main"],"summary":"Get Campaigns","description":"Get campaigns for user's organization","operationId":"get_campaigns_api_v1_campaign__get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}":{"get":{"tags":["main"],"summary":"Get Campaign","description":"Get campaign details","operationId":"get_campaign_api_v1_campaign__campaign_id__get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["main"],"summary":"Update Campaign","description":"Update campaign settings (name, retry config, max concurrency, schedule)","operationId":"update_campaign_api_v1_campaign__campaign_id__patch","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/start":{"post":{"tags":["main"],"summary":"Start Campaign","description":"Start campaign execution","operationId":"start_campaign_api_v1_campaign__campaign_id__start_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/pause":{"post":{"tags":["main"],"summary":"Pause Campaign","description":"Pause campaign execution","operationId":"pause_campaign_api_v1_campaign__campaign_id__pause_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/runs":{"get":{"tags":["main"],"summary":"Get Campaign Runs","description":"Get campaign workflow runs with pagination, filters and sorting","operationId":"get_campaign_runs_api_v1_campaign__campaign_id__runs_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded filter criteria","title":"Filters"},"description":"JSON-encoded filter criteria"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Field to sort by (e.g., 'duration', 'created_at')","title":"Sort By"},"description":"Field to sort by (e.g., 'duration', 'created_at')"},{"name":"sort_order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort order ('asc' or 'desc')","default":"desc","title":"Sort Order"},"description":"Sort order ('asc' or 'desc')"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignRunsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/redial":{"post":{"tags":["main"],"summary":"Redial Campaign","description":"Create a new campaign that re-dials unique subscribers from a completed\ncampaign whose latest call resulted in voicemail, no-answer, or busy.\n\nThe new campaign is created in 'created' state with queued_runs pre-seeded\nfrom the parent's original initial contexts. A campaign can be redialed at\nmost once.","operationId":"redial_campaign_api_v1_campaign__campaign_id__redial_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedialCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/resume":{"post":{"tags":["main"],"summary":"Resume Campaign","description":"Resume a paused campaign","operationId":"resume_campaign_api_v1_campaign__campaign_id__resume_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/progress":{"get":{"tags":["main"],"summary":"Get Campaign Progress","description":"Get current campaign progress and statistics","operationId":"get_campaign_progress_api_v1_campaign__campaign_id__progress_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignProgressResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/source-download-url":{"get":{"tags":["main"],"summary":"Get Campaign Source Download Url","description":"Get presigned download URL for campaign CSV source file\n\nOnly works for CSV source type. For Google Sheets, use the source_id directly.\nValidates that the campaign belongs to the user's organization for security.","operationId":"get_campaign_source_download_url_api_v1_campaign__campaign_id__source_download_url_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSourceDownloadResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/report":{"get":{"tags":["main"],"summary":"Download Campaign Report","description":"Download a CSV report of completed campaign runs.","operationId":"download_campaign_report_api_v1_campaign__campaign_id__report_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or after this datetime (ISO 8601)","title":"Start Date"},"description":"Filter runs created on or after this datetime (ISO 8601)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or before this datetime (ISO 8601)","title":"End Date"},"description":"Filter runs created on or before this datetime (ISO 8601)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credentials/":{"get":{"tags":["main"],"summary":"List Credentials","description":"List all webhook credentials for the user's organization.\n\nReturns:\n List of credentials (without sensitive data)","operationId":"list_credentials_api_v1_credentials__get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CredentialResponse"},"title":"Response List Credentials Api V1 Credentials Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_credentials","x-sdk-description":"List webhook credentials available to the authenticated organization."},"post":{"tags":["main"],"summary":"Create Credential","description":"Create a new webhook credential.\n\nArgs:\n request: The credential creation request\n\nReturns:\n The created credential (without sensitive data)","operationId":"create_credential_api_v1_credentials__post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCredentialRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credentials/{credential_uuid}":{"get":{"tags":["main"],"summary":"Get Credential","description":"Get a specific webhook credential by UUID.\n\nArgs:\n credential_uuid: The UUID of the credential\n\nReturns:\n The credential (without sensitive data)","operationId":"get_credential_api_v1_credentials__credential_uuid__get","parameters":[{"name":"credential_uuid","in":"path","required":true,"schema":{"type":"string","title":"Credential Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update Credential","description":"Update a webhook credential.\n\nArgs:\n credential_uuid: The UUID of the credential to update\n request: The update request\n\nReturns:\n The updated credential (without sensitive data)","operationId":"update_credential_api_v1_credentials__credential_uuid__put","parameters":[{"name":"credential_uuid","in":"path","required":true,"schema":{"type":"string","title":"Credential Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCredentialRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Delete Credential","description":"Delete (soft delete) a webhook credential.\n\nArgs:\n credential_uuid: The UUID of the credential to delete\n\nReturns:\n Success message","operationId":"delete_credential_api_v1_credentials__credential_uuid__delete","parameters":[{"name":"credential_uuid","in":"path","required":true,"schema":{"type":"string","title":"Credential Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Credential Api V1 Credentials Credential Uuid Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/":{"get":{"tags":["main"],"summary":"List Tools","description":"List all tools for the user's organization.\n\nArgs:\n status: Optional filter by status (active, archived, draft)\n category: Optional filter by category (http_api, native, integration)\n\nReturns:\n List of tools","operationId":"list_tools_api_v1_tools__get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ToolResponse"},"title":"Response List Tools Api V1 Tools Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_tools","x-sdk-description":"List tools available to the authenticated organization."},"post":{"tags":["main"],"summary":"Create Tool","description":"Create a new tool.\n\nArgs:\n request: The tool creation request\n\nReturns:\n The created tool","operationId":"create_tool_api_v1_tools__post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateToolRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/{tool_uuid}":{"get":{"tags":["main"],"summary":"Get Tool","description":"Get a specific tool by UUID.\n\nArgs:\n tool_uuid: The UUID of the tool\n\nReturns:\n The tool","operationId":"get_tool_api_v1_tools__tool_uuid__get","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update Tool","description":"Update a tool.\n\nArgs:\n tool_uuid: The UUID of the tool to update\n request: The update request\n\nReturns:\n The updated tool","operationId":"update_tool_api_v1_tools__tool_uuid__put","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateToolRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Delete Tool","description":"Archive (soft delete) a tool.\n\nArgs:\n tool_uuid: The UUID of the tool to delete\n\nReturns:\n Success message","operationId":"delete_tool_api_v1_tools__tool_uuid__delete","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Tool Api V1 Tools Tool Uuid Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/{tool_uuid}/mcp/refresh":{"post":{"tags":["main"],"summary":"Refresh Mcp Tools","description":"Re-discover an MCP tool's server catalog and overwrite the cached\n``definition.config.discovered_tools``. Server down \u2192 200 with error\n(cache not overwritten on transient failure).","operationId":"refresh_mcp_tools_api_v1_tools__tool_uuid__mcp_refresh_post","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpRefreshResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/{tool_uuid}/unarchive":{"post":{"tags":["main"],"summary":"Unarchive Tool","description":"Unarchive a tool (restore from archived state).\n\nArgs:\n tool_uuid: The UUID of the tool to unarchive\n\nReturns:\n The unarchived tool","operationId":"unarchive_tool_api_v1_tools__tool_uuid__unarchive_post","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integration/":{"get":{"tags":["main"],"summary":"Get Integrations","description":"Get all integrations for the user's selected organization.\n\nReturns:\n List of integrations associated with the user's selected organization","operationId":"get_integrations_api_v1_integration__get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/IntegrationResponse"},"title":"Response Get Integrations Api V1 Integration Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integration/session":{"post":{"tags":["main"],"summary":"Create Session","description":"Create a Nango session for the user's selected organization.\n\nReturns:\n Session token and ID for the created session","operationId":"create_session_api_v1_integration_session_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integration/{integration_id}":{"put":{"tags":["main"],"summary":"Update Integration","description":"Update an integration's selected files (for Google Sheets).\n\nArgs:\n integration_id: The ID of the integration to update\n request: The update request containing selected files\n user: The authenticated user\n\nReturns:\n Updated integration details","operationId":"update_integration_api_v1_integration__integration_id__put","parameters":[{"name":"integration_id","in":"path","required":true,"schema":{"type":"integer","title":"Integration Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIntegrationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integration/{integration_id}/access-token":{"get":{"tags":["main"],"summary":"Get Integration Access Token","description":"Get the latest access token for an integration from Nango.\n\nArgs:\n integration_id: The ID of the integration\n user: The authenticated user\n\nReturns:\n Dict containing access token and expiration info","operationId":"get_integration_access_token_api_v1_integration__integration_id__access_token_get","parameters":[{"name":"integration_id","in":"path","required":true,"schema":{"type":"integer","title":"Integration Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessTokenResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-providers/metadata":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Providers Metadata","description":"Return the list of available telephony providers and their form schemas.\n\nThe UI uses this to render the configuration form generically instead of\nhard-coding fields per provider. Adding a new provider only requires\ndeclaring its ui_metadata in providers//__init__.py.","operationId":"get_telephony_providers_metadata_api_v1_organizations_telephony_providers_metadata_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyProvidersMetadataResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-config-warnings":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Config Warnings","description":"Return aggregated warning counts for the current org's telephony configs.\n\nToday this surfaces only Telnyx configs missing ``webhook_public_key``;\nadditional warning types should be added as new fields on the response.","operationId":"get_telephony_config_warnings_api_v1_organizations_telephony_config_warnings_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigWarningsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs":{"get":{"tags":["main","organizations"],"summary":"List Telephony Configurations","description":"List the org's telephony configurations with phone-number counts.","operationId":"list_telephony_configurations_api_v1_organizations_telephony_configs_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationListResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Create Telephony Configuration","description":"Create a new telephony configuration for the org.","operationId":"create_telephony_configuration_api_v1_organizations_telephony_configs_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Configuration By Id","operationId":"get_telephony_configuration_by_id_api_v1_organizations_telephony_configs__config_id__get","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Update Telephony Configuration","operationId":"update_telephony_configuration_api_v1_organizations_telephony_configs__config_id__put","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","organizations"],"summary":"Delete Telephony Configuration","operationId":"delete_telephony_configuration_api_v1_organizations_telephony_configs__config_id__delete","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/set-default-outbound":{"post":{"tags":["main","organizations"],"summary":"Set Default Outbound","operationId":"set_default_outbound_api_v1_organizations_telephony_configs__config_id__set_default_outbound_post","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/phone-numbers":{"get":{"tags":["main","organizations"],"summary":"List Phone Numbers","operationId":"list_phone_numbers_api_v1_organizations_telephony_configs__config_id__phone_numbers_get","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberListResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Create Phone Number","operationId":"create_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers_post","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}":{"get":{"tags":["main","organizations"],"summary":"Get Phone Number","operationId":"get_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__get","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Update Phone Number","operationId":"update_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__put","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","organizations"],"summary":"Delete Phone Number","operationId":"delete_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__delete","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}/set-default-caller":{"post":{"tags":["main","organizations"],"summary":"Set Default Caller Id","operationId":"set_default_caller_id_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__set_default_caller_post","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-config":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Configuration","description":"Legacy: returns the org's default config in the original per-provider\nresponse shape so the existing single-form UI keeps working. Prefer the\nmulti-config endpoints (``/telephony-configs``) for new clients.","operationId":"get_telephony_configuration_api_v1_organizations_telephony_config_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Save Telephony Configuration","description":"Legacy: upserts the org's default config (and its phone numbers) in the\noriginal payload shape so existing UI clients keep working. Prefer the\nmulti-config + phone-number endpoints for new clients.","operationId":"save_telephony_configuration_api_v1_organizations_telephony_config_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ARIConfigurationRequest"},{"$ref":"#/components/schemas/CloudonixConfigurationRequest"},{"$ref":"#/components/schemas/PlivoConfigurationRequest"},{"$ref":"#/components/schemas/TelnyxConfigurationRequest"},{"$ref":"#/components/schemas/TwilioConfigurationRequest"},{"$ref":"#/components/schemas/VobizConfigurationRequest"},{"$ref":"#/components/schemas/VonageConfigurationRequest"}],"discriminator":{"propertyName":"provider","mapping":{"ari":"#/components/schemas/ARIConfigurationRequest","cloudonix":"#/components/schemas/CloudonixConfigurationRequest","plivo":"#/components/schemas/PlivoConfigurationRequest","telnyx":"#/components/schemas/TelnyxConfigurationRequest","twilio":"#/components/schemas/TwilioConfigurationRequest","vobiz":"#/components/schemas/VobizConfigurationRequest","vonage":"#/components/schemas/VonageConfigurationRequest"}},"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/langfuse-credentials":{"get":{"tags":["main","organizations"],"summary":"Get Langfuse Credentials","description":"Get Langfuse credentials for the user's organization with masked sensitive fields.","operationId":"get_langfuse_credentials_api_v1_organizations_langfuse_credentials_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LangfuseCredentialsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Save Langfuse Credentials","description":"Save Langfuse credentials for the user's organization.","operationId":"save_langfuse_credentials_api_v1_organizations_langfuse_credentials_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LangfuseCredentialsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","organizations"],"summary":"Delete Langfuse Credentials","description":"Delete Langfuse credentials for the user's organization.","operationId":"delete_langfuse_credentials_api_v1_organizations_langfuse_credentials_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/campaign-defaults":{"get":{"tags":["main","organizations"],"summary":"Get Campaign Defaults","description":"Get campaign limits for the user's organization.\n\nReturns the organization's concurrent call limit and default retry configuration.","operationId":"get_campaign_defaults_api_v1_organizations_campaign_defaults_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDefaultsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/s3/signed-url":{"get":{"tags":["main","s3"],"summary":"Generate a signed S3 URL","description":"Return a short-lived signed URL for a file stored on S3 / MinIO.\n\nAccess Control:\n* Keys that embed an organization ID (``{prefix}/{org_id}/...``) are\n authorized by matching the org_id against the requesting user's\n organization.\n* Legacy keys (``recordings/{run_id}.wav``, ``transcripts/{run_id}.txt``)\n are authorized via the workflow run they belong to.\n* Superusers can request any key.","operationId":"get_signed_url_api_v1_s3_signed_url_get","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","description":"S3 object key","title":"Key"},"description":"S3 object key"},{"name":"expires_in","in":"query","required":false,"schema":{"type":"integer","default":3600,"title":"Expires In"}},{"name":"inline","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Inline"}},{"name":"storage_backend","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Storage backend to use (e.g. 'minio', 's3'). When omitted the backend is inferred from the resource.","title":"Storage Backend"},"description":"Storage backend to use (e.g. 'minio', 's3'). When omitted the backend is inferred from the resource."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/S3SignedUrlResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/s3/file-metadata":{"get":{"tags":["main","s3"],"summary":"Get file metadata for debugging","description":"Get file metadata including creation timestamp for debugging.\n\nAccess Control:\n* Superusers can request any key.\n* Regular users can only request resources belonging to **their** workflow runs.","operationId":"get_file_metadata_api_v1_s3_file_metadata_get","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","description":"S3 object key","title":"Key"},"description":"S3 object key"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileMetadataResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/s3/presigned-upload-url":{"post":{"tags":["main","s3"],"summary":"Generate a presigned URL for direct CSV upload","description":"Generate a presigned PUT URL for direct CSV file upload to S3/MinIO.\n\nThis endpoint enables browser-to-storage uploads without passing through the backend\n\nAccess Control:\n* All authenticated users can upload CSV files scoped to their organization.\n* Files are stored with organization-scoped keys for multi-tenancy.\n\nReturns:\n* upload_url: Presigned URL (valid for 15 minutes) for PUT request\n* file_key: Unique storage key to use as source_id in campaign creation\n* expires_in: URL expiration time in seconds","operationId":"get_presigned_upload_url_api_v1_s3_presigned_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignedUploadUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignedUploadUrlResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/service-keys":{"get":{"tags":["main"],"summary":"Get Service Keys","description":"Get all service keys for the user's organization.","operationId":"get_service_keys_api_v1_user_service_keys_get","parameters":[{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Archived"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServiceKeyResponse"},"title":"Response Get Service Keys Api V1 User Service Keys Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main"],"summary":"Create Service Key","description":"Create a new service key for the user's organization.","operationId":"create_service_key_api_v1_user_service_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceKeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceKeyResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/service-keys/{service_key_id}":{"delete":{"tags":["main"],"summary":"Archive Service Key","description":"Archive a service key.","operationId":"archive_service_key_api_v1_user_service_keys__service_key_id__delete","parameters":[{"name":"service_key_id","in":"path","required":true,"schema":{"type":"string","title":"Service Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/service-keys/{service_key_id}/reactivate":{"put":{"tags":["main"],"summary":"Reactivate Service Key","description":"Reactivate an archived service key.\n\nNote: This endpoint is provided for API compatibility but service key\nreactivation is not supported by MPS. Once archived, a service key\ncannot be reactivated and a new key must be created instead.","operationId":"reactivate_service_key_api_v1_user_service_keys__service_key_id__reactivate_put","parameters":[{"name":"service_key_id","in":"path","required":true,"schema":{"type":"string","title":"Service Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/current-period":{"get":{"tags":["main"],"summary":"Get Current Period Usage","description":"Get current billing period usage for the user's organization.","operationId":"get_current_period_usage_api_v1_organizations_usage_current_period_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurrentUsageResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/mps-credits":{"get":{"tags":["main"],"summary":"Get Mps Credits","description":"Get aggregated usage and quota from MPS.\n\nOSS users: queries by provider_id (created_by).\nHosted users: queries by organization_id.","operationId":"get_mps_credits_api_v1_organizations_usage_mps_credits_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MPSCreditsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/runs":{"get":{"tags":["main"],"summary":"Get Usage History","description":"Get paginated workflow runs with usage for the organization.","operationId":"get_usage_history_api_v1_organizations_usage_runs_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`.","examples":["2026-04-01T00:00:00Z"],"title":"Start Date"},"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`.","examples":["2026-05-01T00:00:00Z"],"title":"End Date"},"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n","examples":["[{\"attribute\":\"callerNumber\",\"type\":\"text\",\"value\":{\"value\":\"415555\"}}]","[{\"attribute\":\"campaignId\",\"type\":\"number\",\"value\":{\"value\":7}},{\"attribute\":\"duration\",\"type\":\"numberRange\",\"value\":{\"min\":60,\"max\":300}}]","[{\"attribute\":\"dispositionCode\",\"type\":\"multiSelect\",\"value\":{\"codes\":[\"XFER\",\"DNC\"]}}]"],"title":"Filters"},"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageHistoryResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/runs/report":{"get":{"tags":["main"],"summary":"Download Usage Runs Report","description":"Download a CSV of runs matching the same filters as `/usage/runs`.","operationId":"download_usage_runs_report_api_v1_organizations_usage_runs_report_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`.","title":"Start Date"},"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`.","title":"End Date"},"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`."},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n","title":"Filters"},"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/daily-breakdown":{"get":{"tags":["main"],"summary":"Get Daily Usage Breakdown","description":"Get daily usage breakdown for the last N days. Only available for organizations with pricing.","operationId":"get_daily_usage_breakdown_api_v1_organizations_usage_daily_breakdown_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"Number of days to include","default":7,"title":"Days"},"description":"Number of days to include"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailyUsageBreakdownResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/reports/daily":{"get":{"tags":["main"],"summary":"Get Daily Report","description":"Get daily report for the specified date and timezone.\nIf workflow_id is provided, filters results to that specific workflow.\nIf workflow_id is None, includes all workflows for the organization.","operationId":"get_daily_report_api_v1_organizations_reports_daily_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"Date in YYYY-MM-DD format","title":"Date"},"description":"Date in YYYY-MM-DD format"},{"name":"timezone","in":"query","required":true,"schema":{"type":"string","description":"IANA timezone (e.g., 'America/New_York')","title":"Timezone"},"description":"IANA timezone (e.g., 'America/New_York')"},{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Optional workflow ID to filter by","title":"Workflow Id"},"description":"Optional workflow ID to filter by"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailyReportResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/reports/workflows":{"get":{"tags":["main"],"summary":"Get Workflow Options","description":"Get all workflows for the user's organization.\nUsed to populate the workflow selector dropdown in the reports page.","operationId":"get_workflow_options_api_v1_organizations_reports_workflows_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowOption"},"title":"Response Get Workflow Options Api V1 Organizations Reports Workflows Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/reports/daily/runs":{"get":{"tags":["main"],"summary":"Get Daily Runs Detail","description":"Get detailed workflow runs for the specified date.\nUsed for CSV export functionality.","operationId":"get_daily_runs_detail_api_v1_organizations_reports_daily_runs_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"Date in YYYY-MM-DD format","title":"Date"},"description":"Date in YYYY-MM-DD format"},{"name":"timezone","in":"query","required":true,"schema":{"type":"string","description":"IANA timezone (e.g., 'America/New_York')","title":"Timezone"},"description":"IANA timezone (e.g., 'America/New_York')"},{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Optional workflow ID to filter by","title":"Workflow Id"},"description":"Optional workflow ID to filter by"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRunDetail"},"title":"Response Get Daily Runs Detail Api V1 Organizations Reports Daily Runs Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/turn/credentials":{"get":{"tags":["main","turn"],"summary":"Get Turn Credentials","description":"Get time-limited TURN credentials for WebRTC connections.\n\nThis endpoint generates ephemeral TURN credentials that are:\n- Valid for the configured TTL (default: 24 hours)\n- Cryptographically bound to the user via HMAC\n- Compatible with coturn's use-auth-secret mode\n\nReturns:\n TurnCredentialsResponse with username, password, ttl, and TURN URIs","operationId":"get_turn_credentials_api_v1_turn_credentials_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnCredentialsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/embed/init":{"post":{"tags":["main"],"summary":"Initialize Embed Session","description":"Initialize an embed session with token validation and domain checking.\n\nThis endpoint:\n1. Validates the embed token\n2. Checks domain whitelist\n3. Creates a workflow run\n4. Generates a temporary session token\n5. Returns configuration for the widget","operationId":"initialize_embed_session_api_v1_public_embed_init_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitEmbedRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitEmbedResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"options":{"tags":["main"],"summary":"Options Init","description":"Handle CORS preflight for init endpoint","operationId":"options_init_api_v1_public_embed_init_options","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/public/embed/config/{token}":{"get":{"tags":["main"],"summary":"Get Embed Config","description":"Get embed configuration without creating a session.\n\nThis endpoint is used to fetch widget configuration for display purposes\nwithout actually starting a call session.","operationId":"get_embed_config_api_v1_public_embed_config__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedConfigResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"options":{"tags":["main"],"summary":"Options Config","description":"Handle CORS preflight for config endpoint","operationId":"options_config_api_v1_public_embed_config__token__options","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/embed/turn-credentials/{session_token}":{"get":{"tags":["main"],"summary":"Get Public Turn Credentials","description":"Get TURN credentials for an embed session.\n\nThis endpoint allows embedded widgets to obtain TURN server credentials\nfor WebRTC connections without requiring authentication.\n\nArgs:\n session_token: The session token from embed initialization\n\nReturns:\n TurnCredentialsResponse with username, password, ttl, and TURN URIs","operationId":"get_public_turn_credentials_api_v1_public_embed_turn_credentials__session_token__get","parameters":[{"name":"session_token","in":"path","required":true,"schema":{"type":"string","title":"Session Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnCredentialsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"options":{"tags":["main"],"summary":"Options Turn Credentials","description":"Handle CORS preflight for TURN credentials endpoint","operationId":"options_turn_credentials_api_v1_public_embed_turn_credentials__session_token__options","parameters":[{"name":"session_token","in":"path","required":true,"schema":{"type":"string","title":"Session Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/agent/{uuid}":{"post":{"tags":["main"],"summary":"Initiate Call","description":"Initiate a phone call against the published agent.\n\nExecutes the workflow's currently released definition.","operationId":"initiate_call_api_v1_public_agent__uuid__post","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string","title":"Uuid"}},{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/agent/test/{uuid}":{"post":{"tags":["main"],"summary":"Initiate Call Test","description":"Initiate a phone call against the latest draft of the agent.\n\nUseful for verifying changes before publishing. Falls back to the\npublished definition when no draft exists.","operationId":"initiate_call_test_api_v1_public_agent_test__uuid__post","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string","title":"Uuid"}},{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/download/workflow/{token}/{artifact_type}":{"get":{"tags":["main"],"summary":"Download Workflow Artifact","description":"Download a workflow recording or transcript via public access token.\n\nThis endpoint:\n1. Validates the public access token\n2. Looks up the corresponding workflow run\n3. Generates a signed URL for the requested artifact\n4. Redirects to the signed URL\n\nArgs:\n token: The public access token (UUID format)\n artifact_type: Type of artifact - \"recording\" or \"transcript\"\n inline: If true, sets Content-Disposition to inline for browser preview\n\nReturns:\n RedirectResponse to the signed URL (302 redirect)\n\nRaises:\n HTTPException 404: If token is invalid or artifact not found","operationId":"download_workflow_artifact_api_v1_public_download_workflow__token___artifact_type__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"artifact_type","in":"path","required":true,"schema":{"enum":["recording","transcript"],"type":"string","title":"Artifact Type"}},{"name":"inline","in":"query","required":false,"schema":{"type":"boolean","description":"Display inline in browser instead of download","default":false,"title":"Inline"},"description":"Display inline in browser instead of download"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/embed-token":{"post":{"tags":["main"],"summary":"Create Or Update Embed Token","description":"Create or update an embed token for a workflow.\nEach workflow can have only one active embed token.","operationId":"create_or_update_embed_token_api_v1_workflow__workflow_id__embed_token_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedTokenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedTokenResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["main"],"summary":"Get Embed Token","description":"Get the embed token for a workflow if it exists.","operationId":"get_embed_token_api_v1_workflow__workflow_id__embed_token_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/EmbedTokenResponse"},{"type":"null"}],"title":"Response Get Embed Token Api V1 Workflow Workflow Id Embed Token Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Deactivate Embed Token","description":"Deactivate the embed token for a workflow.","operationId":"deactivate_embed_token_api_v1_workflow__workflow_id__embed_token_delete","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Deactivate Embed Token Api V1 Workflow Workflow Id Embed Token Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/upload-url":{"post":{"tags":["main","knowledge-base"],"summary":"Get presigned URL for document upload","description":"Generate a presigned PUT URL for uploading a document.\n\nThis endpoint:\n1. Generates a unique document UUID for organizing the S3 key\n2. Generates a presigned S3/MinIO URL for uploading the file\n3. Returns the upload URL and document metadata\n\nAfter uploading to the returned URL, call /process-document to create\nthe document record and trigger processing.\n\nAccess Control:\n* All authenticated users can upload documents scoped to their organization.","operationId":"get_upload_url_api_v1_knowledge_base_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentUploadRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentUploadResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/process-document":{"post":{"tags":["main","knowledge-base"],"summary":"Trigger document processing","description":"Trigger asynchronous processing of an uploaded document.\n\nThis endpoint should be called after successfully uploading a file to the presigned URL.\nIt will:\n1. Create a document record in the database with the specified UUID\n2. Enqueue a background task to process the document (chunking and embedding)\n\nThe document status will be updated from 'pending' -> 'processing' -> 'completed' or 'failed'.\n\nEmbedding:\nUses OpenAI text-embedding-3-small (1536-dimensional embeddings, requires API key configured in Model Configurations).\n\nAccess Control:\n* Users can only process documents in their organization.","operationId":"process_document_api_v1_knowledge_base_process_document_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessDocumentRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/documents":{"get":{"tags":["main","knowledge-base"],"summary":"List documents","description":"List all documents for the user's organization.\n\nAccess Control:\n* Users can only see documents from their organization.","operationId":"list_documents_api_v1_knowledge_base_documents_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by processing status","title":"Status"},"description":"Filter by processing status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentListResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_documents","x-sdk-description":"List knowledge base documents available to the authenticated organization."}},"/api/v1/knowledge-base/documents/{document_uuid}":{"get":{"tags":["main","knowledge-base"],"summary":"Get document details","description":"Get details of a specific document.\n\nAccess Control:\n* Users can only access documents from their organization.","operationId":"get_document_api_v1_knowledge_base_documents__document_uuid__get","parameters":[{"name":"document_uuid","in":"path","required":true,"schema":{"type":"string","title":"Document Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","knowledge-base"],"summary":"Delete document","description":"Soft delete a document and its chunks.\n\nAccess Control:\n* Users can only delete documents from their organization.","operationId":"delete_document_api_v1_knowledge_base_documents__document_uuid__delete","parameters":[{"name":"document_uuid","in":"path","required":true,"schema":{"type":"string","title":"Document Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/search":{"post":{"tags":["main","knowledge-base"],"summary":"Search for similar chunks","description":"Search for document chunks similar to the query.\n\nThis endpoint uses vector similarity search to find relevant chunks.\nResults are returned without threshold filtering - apply similarity\nthresholds at the application layer after optional reranking.\n\nAccess Control:\n* Users can only search documents from their organization.","operationId":"search_chunks_api_v1_knowledge_base_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChunkSearchRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChunkSearchResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/upload-url":{"post":{"tags":["main","workflow-recordings"],"summary":"Get presigned URLs for recording uploads","description":"Generate presigned PUT URLs for uploading one or more audio recordings.","operationId":"get_upload_urls_api_v1_workflow_recordings_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingUploadRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingUploadResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/":{"post":{"tags":["main","workflow-recordings"],"summary":"Create recording records after upload","description":"Create one or more recording records after audio files have been uploaded.","operationId":"create_recordings_api_v1_workflow_recordings__post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingCreateRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingCreateResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["main","workflow-recordings"],"summary":"List recordings","description":"List recordings for the organization, optionally filtered.","operationId":"list_recordings_api_v1_workflow_recordings__get","parameters":[{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by workflow ID","title":"Workflow Id"},"description":"Filter by workflow ID"},{"name":"tts_provider","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TTS provider","title":"Tts Provider"},"description":"Filter by TTS provider"},{"name":"tts_model","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TTS model","title":"Tts Model"},"description":"Filter by TTS model"},{"name":"tts_voice_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TTS voice ID","title":"Tts Voice Id"},"description":"Filter by TTS voice ID"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingListResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_recordings","x-sdk-description":"List workflow recordings available to the authenticated organization."}},"/api/v1/workflow-recordings/{recording_id}":{"delete":{"tags":["main","workflow-recordings"],"summary":"Delete a recording","description":"Soft delete a recording.","operationId":"delete_recording_api_v1_workflow_recordings__recording_id__delete","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/{id}":{"patch":{"tags":["main","workflow-recordings"],"summary":"Update a recording's Recording ID","description":"Update the recording_id (descriptive name) of a recording.","operationId":"update_recording_api_v1_workflow_recordings__id__patch","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingUpdateRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/transcribe":{"post":{"tags":["main","workflow-recordings"],"summary":"Transcribe an audio file","description":"Transcribe an uploaded audio file using MPS STT.","operationId":"transcribe_audio_api_v1_workflow_recordings_transcribe_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_transcribe_audio_api_v1_workflow_recordings_transcribe_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/signup":{"post":{"tags":["main","auth"],"summary":"Signup","operationId":"signup_api_v1_auth_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["main","auth"],"summary":"Login","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["main","auth"],"summary":"Get Current User","operationId":"get_current_user_api_v1_auth_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/node-types":{"get":{"tags":["main"],"summary":"List Node Types","description":"List every registered NodeSpec.\n\nSDK clients should pin to `spec_version` and warn if the server reports\na higher version than what they were generated against.","operationId":"list_node_types_api_v1_node_types_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeTypesResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_node_types","x-sdk-description":"List every registered node type with its spec. Pinned to spec_version."}},"/api/v1/node-types/{name}":{"get":{"tags":["main"],"summary":"Get Node Type","operationId":"get_node_type_api_v1_node_types__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeSpec"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"get_node_type","x-sdk-description":"Fetch a single node spec by name."}},"/api/v1/health":{"get":{"tags":["main"],"summary":"Health","operationId":"health_api_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}},"404":{"description":"Not found"}}}}},"components":{"schemas":{"APIKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"archived_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Archived At"}},"type":"object","required":["id","name","key_prefix","is_active","created_at"],"title":"APIKeyResponse"},"APIKeyStatus":{"properties":{"model":{"type":"string","title":"Model"},"message":{"type":"string","title":"Message"}},"type":"object","required":["model","message"],"title":"APIKeyStatus"},"APIKeyStatusResponse":{"properties":{"status":{"items":{"$ref":"#/components/schemas/APIKeyStatus"},"type":"array","title":"Status"}},"type":"object","required":["status"],"title":"APIKeyStatusResponse"},"ARIConfigurationRequest":{"properties":{"provider":{"type":"string","const":"ari","title":"Provider","default":"ari"},"ari_endpoint":{"type":"string","title":"Ari Endpoint","description":"ARI base URL (e.g., http://asterisk.example.com:8088)"},"app_name":{"type":"string","title":"App Name","description":"Stasis application name registered in Asterisk"},"app_password":{"type":"string","title":"App Password","description":"ARI user password"},"ws_client_name":{"type":"string","title":"Ws Client Name","description":"websocket_client.conf connection name for externalMedia (e.g., dograh_staging)","default":""},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of SIP extensions/numbers for outbound calls (optional)"}},"type":"object","required":["ari_endpoint","app_name","app_password"],"title":"ARIConfigurationRequest","description":"Request schema for Asterisk ARI configuration."},"ARIConfigurationResponse":{"properties":{"provider":{"type":"string","const":"ari","title":"Provider","default":"ari"},"ari_endpoint":{"type":"string","title":"Ari Endpoint"},"app_name":{"type":"string","title":"App Name"},"app_password":{"type":"string","title":"App Password"},"ws_client_name":{"type":"string","title":"Ws Client Name","default":""},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["ari_endpoint","app_name","app_password","from_numbers"],"title":"ARIConfigurationResponse","description":"Response schema for ARI configuration with masked sensitive fields."},"AccessTokenResponse":{"properties":{"access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Token"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"connection_id":{"type":"string","title":"Connection Id"}},"type":"object","required":["access_token","refresh_token","expires_at","connection_id"],"title":"AccessTokenResponse"},"AmbientNoiseUploadRequest":{"properties":{"workflow_id":{"type":"integer","title":"Workflow Id"},"filename":{"type":"string","title":"Filename"},"mime_type":{"type":"string","title":"Mime Type","default":"audio/wav"},"file_size":{"type":"integer","maximum":10485760.0,"exclusiveMinimum":0.0,"title":"File Size","description":"Max 10MB"}},"type":"object","required":["workflow_id","filename","file_size"],"title":"AmbientNoiseUploadRequest"},"AmbientNoiseUploadResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"storage_key":{"type":"string","title":"Storage Key"},"storage_backend":{"type":"string","title":"Storage Backend"}},"type":"object","required":["upload_url","storage_key","storage_backend"],"title":"AmbientNoiseUploadResponse"},"AuthResponse":{"properties":{"token":{"type":"string","title":"Token"},"user":{"$ref":"#/components/schemas/UserResponse"}},"type":"object","required":["token","user"],"title":"AuthResponse"},"AuthUserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"is_superuser":{"type":"boolean","title":"Is Superuser"}},"type":"object","required":["id","is_superuser"],"title":"AuthUserResponse"},"BatchRecordingCreateRequestSchema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/RecordingCreateRequestSchema"},"type":"array","maxItems":20,"minItems":1,"title":"Recordings","description":"List of recordings to create"}},"type":"object","required":["recordings"],"title":"BatchRecordingCreateRequestSchema","description":"Request schema for creating one or more recording records after upload."},"BatchRecordingCreateResponseSchema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/RecordingResponseSchema"},"type":"array","title":"Recordings","description":"Created recording records"}},"type":"object","required":["recordings"],"title":"BatchRecordingCreateResponseSchema","description":"Response schema for recording creation."},"BatchRecordingUploadRequestSchema":{"properties":{"files":{"items":{"$ref":"#/components/schemas/FileDescriptor"},"type":"array","maxItems":20,"minItems":1,"title":"Files","description":"List of files to upload"}},"type":"object","required":["files"],"title":"BatchRecordingUploadRequestSchema","description":"Request schema for getting presigned upload URLs for one or more files."},"BatchRecordingUploadResponseSchema":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RecordingUploadResponseSchema"},"type":"array","title":"Items","description":"Upload URLs for each file"}},"type":"object","required":["items"],"title":"BatchRecordingUploadResponseSchema","description":"Response schema with presigned upload URLs."},"Body_transcribe_audio_api_v1_workflow_recordings_transcribe_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"language":{"type":"string","title":"Language","default":"en"}},"type":"object","required":["file"],"title":"Body_transcribe_audio_api_v1_workflow_recordings_transcribe_post"},"CalculatorToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"calculator","title":"Type","description":"Tool type"}},"type":"object","required":["type"],"title":"CalculatorToolDefinition","description":"Tool definition for Calculator tools (no configuration needed)."},"CallDispositionCodes":{"properties":{"disposition_codes":{"items":{"type":"string"},"type":"array","title":"Disposition Codes","default":[]}},"type":"object","title":"CallDispositionCodes"},"CallType":{"type":"string","enum":["inbound","outbound"],"title":"CallType"},"CampaignDefaultsResponse":{"properties":{"concurrent_call_limit":{"type":"integer","title":"Concurrent Call Limit"},"from_numbers_count":{"type":"integer","title":"From Numbers Count"},"default_retry_config":{"$ref":"#/components/schemas/RetryConfigResponse"},"last_campaign_settings":{"anyOf":[{"$ref":"#/components/schemas/LastCampaignSettingsResponse"},{"type":"null"}]}},"type":"object","required":["concurrent_call_limit","from_numbers_count","default_retry_config"],"title":"CampaignDefaultsResponse"},"CampaignLogEntryResponse":{"properties":{"ts":{"type":"string","title":"Ts"},"level":{"type":"string","title":"Level"},"event":{"type":"string","title":"Event"},"message":{"type":"string","title":"Message"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"}},"type":"object","required":["ts","level","event","message"],"title":"CampaignLogEntryResponse","description":"A single timestamped entry from the campaign's append-only log.\n\nSurfaced in the UI so operators can see why a campaign moved to\npaused / failed without digging through server logs."},"CampaignProgressResponse":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"state":{"type":"string","title":"State"},"total_rows":{"type":"integer","title":"Total Rows"},"processed_rows":{"type":"integer","title":"Processed Rows"},"failed_calls":{"type":"integer","title":"Failed Calls"},"progress_percentage":{"type":"number","title":"Progress Percentage"},"source_sync":{"additionalProperties":true,"type":"object","title":"Source Sync"},"rate_limit":{"type":"integer","title":"Rate Limit"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["campaign_id","state","total_rows","processed_rows","failed_calls","progress_percentage","source_sync","rate_limit","started_at","completed_at"],"title":"CampaignProgressResponse"},"CampaignResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_name":{"type":"string","title":"Workflow Name"},"state":{"type":"string","title":"State"},"source_type":{"type":"string","title":"Source Type"},"source_id":{"type":"string","title":"Source Id"},"total_rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Rows"},"processed_rows":{"type":"integer","title":"Processed Rows"},"failed_rows":{"type":"integer","title":"Failed Rows"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"retry_config":{"$ref":"#/components/schemas/RetryConfigResponse"},"max_concurrency":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigResponse"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigResponse"},{"type":"null"}]},"executed_count":{"type":"integer","title":"Executed Count","default":0},"total_queued_count":{"type":"integer","title":"Total Queued Count","default":0},"parent_campaign_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent Campaign Id"},"redialed_campaign_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Redialed Campaign Id"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"},"telephony_configuration_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Telephony Configuration Name"},"logs":{"items":{"$ref":"#/components/schemas/CampaignLogEntryResponse"},"type":"array","title":"Logs"}},"type":"object","required":["id","name","workflow_id","workflow_name","state","source_type","source_id","total_rows","processed_rows","failed_rows","created_at","started_at","completed_at","retry_config"],"title":"CampaignResponse"},"CampaignRunsResponse":{"properties":{"runs":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Runs"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["runs","total_count","page","limit","total_pages"],"title":"CampaignRunsResponse","description":"Paginated response for campaign workflow runs"},"CampaignSourceDownloadResponse":{"properties":{"download_url":{"type":"string","title":"Download Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["download_url","expires_in"],"title":"CampaignSourceDownloadResponse"},"CampaignsResponse":{"properties":{"campaigns":{"items":{"$ref":"#/components/schemas/CampaignResponse"},"type":"array","title":"Campaigns"}},"type":"object","required":["campaigns"],"title":"CampaignsResponse"},"ChunkResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"document_id":{"type":"integer","title":"Document Id"},"chunk_text":{"type":"string","title":"Chunk Text"},"contextualized_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contextualized Text"},"chunk_index":{"type":"integer","title":"Chunk Index"},"chunk_metadata":{"additionalProperties":true,"type":"object","title":"Chunk Metadata"},"filename":{"type":"string","title":"Filename"},"document_uuid":{"type":"string","title":"Document Uuid"},"similarity":{"type":"number","title":"Similarity"}},"type":"object","required":["id","document_id","chunk_text","contextualized_text","chunk_index","chunk_metadata","filename","document_uuid","similarity"],"title":"ChunkResponseSchema","description":"Response schema for a document chunk."},"ChunkSearchRequestSchema":{"properties":{"query":{"type":"string","title":"Query","description":"Search query text"},"limit":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Limit","description":"Maximum number of results","default":5},"document_uuids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Document Uuids","description":"Filter by specific document UUIDs"},"min_similarity":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Min Similarity","description":"Minimum similarity threshold"}},"type":"object","required":["query"],"title":"ChunkSearchRequestSchema","description":"Request schema for searching similar chunks."},"ChunkSearchResponseSchema":{"properties":{"chunks":{"items":{"$ref":"#/components/schemas/ChunkResponseSchema"},"type":"array","title":"Chunks"},"query":{"type":"string","title":"Query"},"total_results":{"type":"integer","title":"Total Results"}},"type":"object","required":["chunks","query","total_results"],"title":"ChunkSearchResponseSchema","description":"Response schema for chunk search results."},"CircuitBreakerConfigRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"failure_threshold":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Failure Threshold","default":0.5},"window_seconds":{"type":"integer","maximum":600.0,"minimum":30.0,"title":"Window Seconds","default":120},"min_calls_in_window":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Min Calls In Window","default":5}},"type":"object","title":"CircuitBreakerConfigRequest"},"CircuitBreakerConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":false},"failure_threshold":{"type":"number","title":"Failure Threshold","default":0.5},"window_seconds":{"type":"integer","title":"Window Seconds","default":120},"min_calls_in_window":{"type":"integer","title":"Min Calls In Window","default":5}},"type":"object","title":"CircuitBreakerConfigResponse"},"CloudonixConfigurationRequest":{"properties":{"provider":{"type":"string","const":"cloudonix","title":"Provider","default":"cloudonix"},"bearer_token":{"type":"string","title":"Bearer Token","description":"Cloudonix API Bearer Token"},"domain_id":{"type":"string","title":"Domain Id","description":"Cloudonix Domain ID"},"application_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Name","description":"Cloudonix Voice Application name. The application's url is updated when inbound workflows are attached to numbers on this domain. If omitted, an application is auto-created on save and its name is stored on the configuration."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Cloudonix phone numbers (optional)"}},"type":"object","required":["bearer_token","domain_id"],"title":"CloudonixConfigurationRequest","description":"Request schema for Cloudonix configuration."},"CloudonixConfigurationResponse":{"properties":{"provider":{"type":"string","const":"cloudonix","title":"Provider","default":"cloudonix"},"bearer_token":{"type":"string","title":"Bearer Token"},"domain_id":{"type":"string","title":"Domain Id"},"application_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Name"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["bearer_token","domain_id","from_numbers"],"title":"CloudonixConfigurationResponse","description":"Response schema for Cloudonix configuration with masked sensitive fields."},"CreateAPIKeyRequest":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"CreateAPIKeyRequest"},"CreateAPIKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"api_key":{"type":"string","title":"Api Key"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","key_prefix","api_key","created_at"],"title":"CreateAPIKeyResponse"},"CreateCampaignRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"workflow_id":{"type":"integer","title":"Workflow Id"},"source_type":{"type":"string","pattern":"^(google-sheet|csv)$","title":"Source Type"},"source_id":{"type":"string","title":"Source Id"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"},"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigRequest"},{"type":"null"}]},"max_concurrency":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":1.0},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigRequest"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigRequest"},{"type":"null"}]}},"type":"object","required":["name","workflow_id","source_type","source_id"],"title":"CreateCampaignRequest"},"CreateCredentialRequest":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"credential_type":{"$ref":"#/components/schemas/WebhookCredentialType"},"credential_data":{"additionalProperties":true,"type":"object","title":"Credential Data"}},"type":"object","required":["name","credential_type","credential_data"],"title":"CreateCredentialRequest","description":"Request schema for creating a webhook credential."},"CreateServiceKeyRequest":{"properties":{"name":{"type":"string","title":"Name"},"expires_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In Days","default":90}},"type":"object","required":["name"],"title":"CreateServiceKeyRequest"},"CreateServiceKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"service_key":{"type":"string","title":"Service Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","name","service_key","key_prefix"],"title":"CreateServiceKeyResponse"},"CreateToolRequest":{"properties":{"name":{"type":"string","maxLength":255,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"type":"string","title":"Category","default":"http_api"},"icon":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Icon","default":"globe"},"icon_color":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Icon Color","default":"#3B82F6"},"definition":{"oneOf":[{"$ref":"#/components/schemas/HttpApiToolDefinition"},{"$ref":"#/components/schemas/EndCallToolDefinition"},{"$ref":"#/components/schemas/TransferCallToolDefinition"},{"$ref":"#/components/schemas/CalculatorToolDefinition"},{"$ref":"#/components/schemas/McpToolDefinition"}],"title":"Definition","discriminator":{"propertyName":"type","mapping":{"calculator":"#/components/schemas/CalculatorToolDefinition","end_call":"#/components/schemas/EndCallToolDefinition","http_api":"#/components/schemas/HttpApiToolDefinition","mcp":"#/components/schemas/McpToolDefinition","transfer_call":"#/components/schemas/TransferCallToolDefinition"}}}},"type":"object","required":["name","definition"],"title":"CreateToolRequest","description":"Request schema for creating a tool."},"CreateWorkflowRequest":{"properties":{"name":{"type":"string","title":"Name"},"workflow_definition":{"additionalProperties":true,"type":"object","title":"Workflow Definition"}},"type":"object","required":["name","workflow_definition"],"title":"CreateWorkflowRequest"},"CreateWorkflowRunRequest":{"properties":{"mode":{"type":"string","title":"Mode"},"name":{"type":"string","title":"Name"}},"type":"object","required":["mode","name"],"title":"CreateWorkflowRunRequest"},"CreateWorkflowRunResponse":{"properties":{"id":{"type":"integer","title":"Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"name":{"type":"string","title":"Name"},"mode":{"type":"string","title":"Mode"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"definition_id":{"type":"integer","title":"Definition Id"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"}},"type":"object","required":["id","workflow_id","name","mode","created_at","definition_id"],"title":"CreateWorkflowRunResponse"},"CreateWorkflowTemplateRequest":{"properties":{"call_type":{"type":"string","enum":["inbound","outbound"],"title":"Call Type"},"use_case":{"type":"string","title":"Use Case"},"activity_description":{"type":"string","title":"Activity Description"}},"type":"object","required":["call_type","use_case","activity_description"],"title":"CreateWorkflowTemplateRequest"},"CreatedByResponse":{"properties":{"id":{"type":"integer","title":"Id"},"provider_id":{"type":"string","title":"Provider Id"}},"type":"object","required":["id","provider_id"],"title":"CreatedByResponse","description":"Response schema for the user who created a tool."},"CredentialResponse":{"properties":{"uuid":{"type":"string","title":"Uuid"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"credential_type":{"type":"string","title":"Credential Type"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["uuid","name","description","credential_type","created_at","updated_at"],"title":"CredentialResponse","description":"Response schema for a webhook credential (never includes sensitive data)."},"CurrentUsageResponse":{"properties":{"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"used_dograh_tokens":{"type":"number","title":"Used Dograh Tokens"},"quota_dograh_tokens":{"type":"integer","title":"Quota Dograh Tokens"},"percentage_used":{"type":"number","title":"Percentage Used"},"next_refresh_date":{"type":"string","title":"Next Refresh Date"},"quota_enabled":{"type":"boolean","title":"Quota Enabled"},"total_duration_seconds":{"type":"integer","title":"Total Duration Seconds"},"used_amount_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Used Amount Usd"},"quota_amount_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quota Amount Usd"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"price_per_second_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price Per Second Usd"}},"type":"object","required":["period_start","period_end","used_dograh_tokens","quota_dograh_tokens","percentage_used","next_refresh_date","quota_enabled","total_duration_seconds"],"title":"CurrentUsageResponse"},"DailyReportResponse":{"properties":{"date":{"type":"string","title":"Date"},"timezone":{"type":"string","title":"Timezone"},"workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Id"},"metrics":{"additionalProperties":{"type":"integer"},"type":"object","title":"Metrics"},"disposition_distribution":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Disposition Distribution"},"call_duration_distribution":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Call Duration Distribution"}},"type":"object","required":["date","timezone","workflow_id","metrics","disposition_distribution","call_duration_distribution"],"title":"DailyReportResponse"},"DailyUsageBreakdownResponse":{"properties":{"breakdown":{"items":{"$ref":"#/components/schemas/DailyUsageItem"},"type":"array","title":"Breakdown"},"total_minutes":{"type":"number","title":"Total Minutes"},"total_cost_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Cost Usd"},"total_dograh_tokens":{"type":"number","title":"Total Dograh Tokens"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"}},"type":"object","required":["breakdown","total_minutes","total_dograh_tokens"],"title":"DailyUsageBreakdownResponse"},"DailyUsageItem":{"properties":{"date":{"type":"string","title":"Date"},"minutes":{"type":"number","title":"Minutes"},"cost_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Usd"},"dograh_tokens":{"type":"number","title":"Dograh Tokens"},"call_count":{"type":"integer","title":"Call Count"}},"type":"object","required":["date","minutes","dograh_tokens","call_count"],"title":"DailyUsageItem"},"DefaultConfigurationsResponse":{"properties":{"llm":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Llm"},"tts":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Tts"},"stt":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Stt"},"embeddings":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Embeddings"},"realtime":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Realtime"},"default_providers":{"additionalProperties":{"type":"string"},"type":"object","title":"Default Providers"}},"type":"object","required":["llm","tts","stt","embeddings","realtime","default_providers"],"title":"DefaultConfigurationsResponse"},"DisplayOptions":{"properties":{"show":{"anyOf":[{"additionalProperties":{"items":{},"type":"array"},"type":"object"},{"type":"null"}],"title":"Show"},"hide":{"anyOf":[{"additionalProperties":{"items":{},"type":"array"},"type":"object"},{"type":"null"}],"title":"Hide"}},"additionalProperties":false,"type":"object","title":"DisplayOptions","description":"Conditional visibility rules.\n\n`show` keys are AND-combined: this property is visible only when EVERY\nreferenced field's value matches one of the listed values.\n\n`hide` keys are OR-combined: this property is hidden when ANY referenced\nfield's value matches one of the listed values.\n\nExample:\n DisplayOptions(show={\"extraction_enabled\": [True]})\n DisplayOptions(show={\"greeting_type\": [\"audio\"]})"},"DocumentListResponseSchema":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/DocumentResponseSchema"},"type":"array","title":"Documents"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["documents","total","limit","offset"],"title":"DocumentListResponseSchema","description":"Response schema for list of documents."},"DocumentResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"document_uuid":{"type":"string","title":"Document Uuid"},"filename":{"type":"string","title":"Filename"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"file_hash":{"type":"string","title":"File Hash"},"mime_type":{"type":"string","title":"Mime Type"},"processing_status":{"type":"string","title":"Processing Status"},"processing_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Processing Error"},"total_chunks":{"type":"integer","title":"Total Chunks"},"retrieval_mode":{"type":"string","title":"Retrieval Mode","default":"chunked"},"custom_metadata":{"additionalProperties":true,"type":"object","title":"Custom Metadata"},"docling_metadata":{"additionalProperties":true,"type":"object","title":"Docling Metadata"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"organization_id":{"type":"integer","title":"Organization Id"},"created_by":{"type":"integer","title":"Created By"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","document_uuid","filename","file_size_bytes","file_hash","mime_type","processing_status","total_chunks","custom_metadata","docling_metadata","created_at","updated_at","organization_id","created_by","is_active"],"title":"DocumentResponseSchema","description":"Response schema for document metadata."},"DocumentUploadRequestSchema":{"properties":{"filename":{"type":"string","title":"Filename","description":"Name of the file to upload"},"mime_type":{"type":"string","title":"Mime Type","description":"MIME type of the file"},"custom_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Custom Metadata","description":"Optional custom metadata"}},"type":"object","required":["filename","mime_type"],"title":"DocumentUploadRequestSchema","description":"Request schema for initiating document upload."},"DocumentUploadResponseSchema":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Signed URL for uploading the file"},"document_uuid":{"type":"string","title":"Document Uuid","description":"Unique identifier for the document"},"s3_key":{"type":"string","title":"S3 Key","description":"S3 key where file should be uploaded"}},"type":"object","required":["upload_url","document_uuid","s3_key"],"title":"DocumentUploadResponseSchema","description":"Response schema containing upload URL and document metadata."},"DuplicateTemplateRequest":{"properties":{"template_id":{"type":"integer","title":"Template Id"},"workflow_name":{"type":"string","title":"Workflow Name"}},"type":"object","required":["template_id","workflow_name"],"title":"DuplicateTemplateRequest"},"EmbedConfigResponse":{"properties":{"workflow_id":{"type":"integer","title":"Workflow Id"},"settings":{"additionalProperties":true,"type":"object","title":"Settings"},"theme":{"type":"string","title":"Theme"},"position":{"type":"string","title":"Position"},"button_text":{"type":"string","title":"Button Text"},"button_color":{"type":"string","title":"Button Color"},"size":{"type":"string","title":"Size"},"auto_start":{"type":"boolean","title":"Auto Start"}},"type":"object","required":["workflow_id","settings","theme","position","button_text","button_color","size","auto_start"],"title":"EmbedConfigResponse","description":"Response model for embed configuration"},"EmbedTokenRequest":{"properties":{"allowed_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Domains"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"usage_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Usage Limit"},"expires_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In Days","default":30}},"type":"object","title":"EmbedTokenRequest"},"EmbedTokenResponse":{"properties":{"id":{"type":"integer","title":"Id"},"token":{"type":"string","title":"Token"},"allowed_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Domains"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"is_active":{"type":"boolean","title":"Is Active"},"usage_count":{"type":"integer","title":"Usage Count"},"usage_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Usage Limit"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"embed_script":{"type":"string","title":"Embed Script"}},"type":"object","required":["id","token","allowed_domains","settings","is_active","usage_count","usage_limit","expires_at","created_at","embed_script"],"title":"EmbedTokenResponse"},"EndCallConfig":{"properties":{"messageType":{"type":"string","enum":["none","custom","audio"],"title":"Messagetype","description":"Type of goodbye message","default":"none"},"customMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessage","description":"Custom message to play before ending the call"},"audioRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audiorecordingid","description":"Recording ID for audio goodbye message"},"endCallReason":{"type":"boolean","title":"Endcallreason","description":"When enabled, LLM must provide a reason for ending the call. The reason is set as call disposition and added to call tags.","default":false},"endCallReasonDescription":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endcallreasondescription","description":"Description shown to the LLM for the reason parameter. Used only when endCallReason is enabled."}},"type":"object","title":"EndCallConfig","description":"Configuration for End Call tools."},"EndCallToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"end_call","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/EndCallConfig","description":"End Call configuration"}},"type":"object","required":["type","config"],"title":"EndCallToolDefinition","description":"Tool definition for End Call tools."},"FileDescriptor":{"properties":{"filename":{"type":"string","title":"Filename","description":"Original filename of the audio file"},"mime_type":{"type":"string","title":"Mime Type","description":"MIME type of the audio file","default":"audio/wav"},"file_size":{"type":"integer","maximum":5242880.0,"exclusiveMinimum":0.0,"title":"File Size","description":"File size in bytes (max 5MB)"}},"type":"object","required":["filename","file_size"],"title":"FileDescriptor","description":"Descriptor for a single file in a batch upload request."},"FileMetadataResponse":{"properties":{"key":{"type":"string","title":"Key"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["key","metadata"],"title":"FileMetadataResponse"},"GraphConstraints":{"properties":{"min_incoming":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Incoming"},"max_incoming":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Incoming"},"min_outgoing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Outgoing"},"max_outgoing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Outgoing"}},"additionalProperties":false,"type":"object","title":"GraphConstraints","description":"Per-node-type graph rules. WorkflowGraph enforces these at validation."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"version":{"type":"string","title":"Version"},"backend_api_endpoint":{"type":"string","title":"Backend Api Endpoint"},"deployment_mode":{"type":"string","title":"Deployment Mode"},"auth_provider":{"type":"string","title":"Auth Provider"},"turn_enabled":{"type":"boolean","title":"Turn Enabled"},"force_turn_relay":{"type":"boolean","title":"Force Turn Relay"}},"type":"object","required":["status","version","backend_api_endpoint","deployment_mode","auth_provider","turn_enabled","force_turn_relay"],"title":"HealthResponse"},"HttpApiConfig":{"properties":{"method":{"type":"string","title":"Method","description":"HTTP method (GET, POST, PUT, PATCH, DELETE)"},"url":{"type":"string","title":"Url","description":"Target URL"},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers","description":"Static headers to include"},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to ExternalCredentialModel for auth"},"parameters":{"anyOf":[{"items":{"$ref":"#/components/schemas/ToolParameter"},"type":"array"},{"type":"null"}],"title":"Parameters","description":"Parameters that the tool accepts from LLM"},"preset_parameters":{"anyOf":[{"items":{"$ref":"#/components/schemas/PresetToolParameter"},"type":"array"},{"type":"null"}],"title":"Preset Parameters","description":"Parameters injected by Dograh from fixed values or workflow context templates"},"timeout_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timeout Ms","description":"Request timeout in milliseconds","default":5000},"customMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessage","description":"Custom message to play after tool execution"},"customMessageType":{"anyOf":[{"type":"string","enum":["text","audio"]},{"type":"null"}],"title":"Custommessagetype","description":"Type of custom message: text or audio"},"customMessageRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessagerecordingid","description":"Recording ID for audio custom message"}},"type":"object","required":["method","url"],"title":"HttpApiConfig","description":"Configuration for HTTP API tools."},"HttpApiToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"http_api","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/HttpApiConfig","description":"HTTP API configuration"}},"type":"object","required":["type","config"],"title":"HttpApiToolDefinition","description":"Tool definition for HTTP API tools."},"ImpersonateRequest":{"properties":{"provider_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider User Id"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"}},"type":"object","title":"ImpersonateRequest","description":"Request payload for superadmin impersonation.\n\nEither ``provider_user_id`` **or** ``user_id`` must be supplied. If both are\nprovided, ``provider_user_id`` takes precedence."},"ImpersonateResponse":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"},"access_token":{"type":"string","title":"Access Token"}},"type":"object","required":["refresh_token","access_token"],"title":"ImpersonateResponse"},"InitEmbedRequest":{"properties":{"token":{"type":"string","title":"Token"},"context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Context Variables"}},"type":"object","required":["token"],"title":"InitEmbedRequest","description":"Request model for initializing an embed session"},"InitEmbedResponse":{"properties":{"session_token":{"type":"string","title":"Session Token"},"workflow_run_id":{"type":"integer","title":"Workflow Run Id"},"config":{"additionalProperties":true,"type":"object","title":"Config"}},"type":"object","required":["session_token","workflow_run_id","config"],"title":"InitEmbedResponse","description":"Response model for embed initialization"},"InitiateCallRequest":{"properties":{"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_run_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Run Id"},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"},"from_phone_number_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"From Phone Number Id"}},"type":"object","required":["workflow_id"],"title":"InitiateCallRequest"},"IntegrationResponse":{"properties":{"id":{"type":"integer","title":"Id"},"integration_id":{"type":"string","title":"Integration Id"},"organization_id":{"type":"integer","title":"Organization Id"},"created_by":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By"},"provider":{"type":"string","title":"Provider"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","title":"Created At"},"action":{"type":"string","title":"Action"},"provider_data":{"additionalProperties":true,"type":"object","title":"Provider Data"}},"type":"object","required":["id","integration_id","organization_id","created_by","provider","is_active","created_at","action","provider_data"],"title":"IntegrationResponse"},"ItemKind":{"type":"string","enum":["node","edge","workflow"],"title":"ItemKind"},"LangfuseCredentialsRequest":{"properties":{"host":{"type":"string","title":"Host"},"public_key":{"type":"string","title":"Public Key"},"secret_key":{"type":"string","title":"Secret Key"}},"type":"object","required":["host","public_key","secret_key"],"title":"LangfuseCredentialsRequest"},"LangfuseCredentialsResponse":{"properties":{"host":{"type":"string","title":"Host","default":""},"public_key":{"type":"string","title":"Public Key","default":""},"secret_key":{"type":"string","title":"Secret Key","default":""},"configured":{"type":"boolean","title":"Configured","default":false}},"type":"object","title":"LangfuseCredentialsResponse"},"LastCampaignSettingsResponse":{"properties":{"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigResponse"},{"type":"null"}]},"max_concurrency":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigResponse"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigResponse"},{"type":"null"}]}},"type":"object","title":"LastCampaignSettingsResponse"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"MPSCreditsResponse":{"properties":{"total_credits_used":{"type":"number","title":"Total Credits Used"},"remaining_credits":{"type":"number","title":"Remaining Credits"},"total_quota":{"type":"number","title":"Total Quota"}},"type":"object","required":["total_credits_used","remaining_credits","total_quota"],"title":"MPSCreditsResponse"},"McpRefreshResponse":{"properties":{"tool_uuid":{"type":"string","title":"Tool Uuid"},"discovered_tools":{"items":{},"type":"array","title":"Discovered Tools"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["tool_uuid"],"title":"McpRefreshResponse","description":"Result of re-discovering an MCP server's tool catalog."},"McpToolConfig":{"properties":{"transport":{"type":"string","const":"streamable_http","title":"Transport","description":"MCP transport protocol","default":"streamable_http"},"url":{"type":"string","title":"Url","description":"MCP server URL (must be http:// or https://)"},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to ExternalCredentialModel for auth"},"tools_filter":{"items":{"type":"string"},"type":"array","title":"Tools Filter","description":"Allowlist of MCP tool names to expose (empty = all tools)"},"timeout_secs":{"type":"integer","title":"Timeout Secs","description":"Connection timeout in seconds","default":30},"sse_read_timeout_secs":{"type":"integer","title":"Sse Read Timeout Secs","description":"SSE read timeout in seconds","default":300},"discovered_tools":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Discovered Tools","description":"Server-managed cache of the MCP server's tool catalog [{name, description}]. Populated best-effort by the backend."}},"type":"object","required":["url"],"title":"McpToolConfig","description":"Configuration for an MCP tool definition."},"McpToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"mcp","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/McpToolConfig","description":"MCP server configuration"}},"type":"object","required":["type","config"],"title":"McpToolDefinition","description":"Persisted MCP tool definition."},"NodeCategory":{"type":"string","enum":["call_node","global_node","trigger","integration"],"title":"NodeCategory","description":"Drives grouping in the AddNodePanel UI."},"NodeExample":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"additionalProperties":false,"type":"object","required":["name","data"],"title":"NodeExample","description":"A worked example LLMs can pattern-match. Keep small and realistic."},"NodeSpec":{"properties":{"name":{"type":"string","title":"Name"},"display_name":{"type":"string","title":"Display Name"},"description":{"type":"string","minLength":1,"title":"Description","description":"Human-facing explanation shown in AddNodePanel."},"llm_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Hint","description":"LLM-only guidance; omitted from the UI."},"category":{"$ref":"#/components/schemas/NodeCategory"},"icon":{"type":"string","title":"Icon"},"version":{"type":"string","title":"Version","default":"1.0.0"},"properties":{"items":{"$ref":"#/components/schemas/PropertySpec"},"type":"array","title":"Properties"},"examples":{"items":{"$ref":"#/components/schemas/NodeExample"},"type":"array","title":"Examples"},"graph_constraints":{"anyOf":[{"$ref":"#/components/schemas/GraphConstraints"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["name","display_name","description","category","icon","properties"],"title":"NodeSpec","description":"Single source of truth for a node type."},"NodeTypesResponse":{"properties":{"spec_version":{"type":"string","title":"Spec Version"},"node_types":{"items":{"$ref":"#/components/schemas/NodeSpec"},"type":"array","title":"Node Types"}},"type":"object","required":["spec_version","node_types"],"title":"NodeTypesResponse"},"PhoneNumberCreateRequest":{"properties":{"address":{"type":"string","maxLength":255,"minLength":1,"title":"Address"},"country_code":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country Code"},"label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Label"},"inbound_workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inbound Workflow Id"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_default_caller_id":{"type":"boolean","title":"Is Default Caller Id","default":false},"extra_metadata":{"additionalProperties":true,"type":"object","title":"Extra Metadata"}},"type":"object","required":["address"],"title":"PhoneNumberCreateRequest","description":"Create a new phone number under a telephony configuration.\n\n``address_normalized`` and ``address_type`` are computed server-side from\n``address`` (and ``country_code`` if PSTN). ``address`` itself is stored\nverbatim for display."},"PhoneNumberListResponse":{"properties":{"phone_numbers":{"items":{"$ref":"#/components/schemas/PhoneNumberResponse"},"type":"array","title":"Phone Numbers"}},"type":"object","required":["phone_numbers"],"title":"PhoneNumberListResponse"},"PhoneNumberResponse":{"properties":{"id":{"type":"integer","title":"Id"},"telephony_configuration_id":{"type":"integer","title":"Telephony Configuration Id"},"address":{"type":"string","title":"Address"},"address_normalized":{"type":"string","title":"Address Normalized"},"address_type":{"type":"string","title":"Address Type"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"inbound_workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inbound Workflow Id"},"inbound_workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inbound Workflow Name"},"is_active":{"type":"boolean","title":"Is Active"},"is_default_caller_id":{"type":"boolean","title":"Is Default Caller Id"},"extra_metadata":{"additionalProperties":true,"type":"object","title":"Extra Metadata"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"provider_sync":{"anyOf":[{"$ref":"#/components/schemas/ProviderSyncStatus"},{"type":"null"}]}},"type":"object","required":["id","telephony_configuration_id","address","address_normalized","address_type","is_active","is_default_caller_id","extra_metadata","created_at","updated_at"],"title":"PhoneNumberResponse"},"PhoneNumberUpdateRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Label"},"inbound_workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inbound Workflow Id"},"clear_inbound_workflow":{"type":"boolean","title":"Clear Inbound Workflow","default":false},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"country_code":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country Code"},"extra_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Extra Metadata"}},"type":"object","title":"PhoneNumberUpdateRequest","description":"Partial update. ``address`` is intentionally immutable \u2014 to change a\nnumber, delete the row and create a new one."},"PlivoConfigurationRequest":{"properties":{"provider":{"type":"string","const":"plivo","title":"Provider","default":"plivo"},"auth_id":{"type":"string","title":"Auth Id","description":"Plivo Auth ID"},"auth_token":{"type":"string","title":"Auth Token","description":"Plivo Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id","description":"Plivo Application ID. The application's answer_url is updated when inbound workflows are attached to numbers on this account. If omitted, an application is auto-created on save and its id is stored on the configuration."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Plivo phone numbers"}},"type":"object","required":["auth_id","auth_token"],"title":"PlivoConfigurationRequest","description":"Request schema for Plivo configuration."},"PlivoConfigurationResponse":{"properties":{"provider":{"type":"string","const":"plivo","title":"Provider","default":"plivo"},"auth_id":{"type":"string","title":"Auth Id"},"auth_token":{"type":"string","title":"Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["auth_id","auth_token","from_numbers"],"title":"PlivoConfigurationResponse","description":"Response schema for Plivo configuration with masked sensitive fields."},"PresetToolParameter":{"properties":{"name":{"type":"string","title":"Name","description":"Parameter name (used as key in request body)"},"type":{"type":"string","title":"Type","description":"Parameter type: string, number, or boolean"},"value_template":{"type":"string","title":"Value Template","description":"Fixed value or template, e.g. {{initial_context.phone_number}}"},"required":{"type":"boolean","title":"Required","description":"Whether the parameter must resolve to a non-empty value","default":true}},"type":"object","required":["name","type","value_template"],"title":"PresetToolParameter","description":"A parameter injected by Dograh at runtime."},"PresignedUploadUrlRequest":{"properties":{"file_name":{"type":"string","pattern":".*\\.csv$","title":"File Name","description":"CSV filename"},"file_size":{"type":"integer","maximum":10485760.0,"exclusiveMinimum":0.0,"title":"File Size","description":"File size in bytes (max 10MB)"},"content_type":{"type":"string","title":"Content Type","description":"File content type","default":"text/csv"}},"type":"object","required":["file_name","file_size"],"title":"PresignedUploadUrlRequest"},"PresignedUploadUrlResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"file_key":{"type":"string","title":"File Key"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["upload_url","file_key","expires_in"],"title":"PresignedUploadUrlResponse"},"ProcessDocumentRequestSchema":{"properties":{"document_uuid":{"type":"string","title":"Document Uuid","description":"Document UUID to process"},"s3_key":{"type":"string","title":"S3 Key","description":"S3 key of the uploaded file"},"retrieval_mode":{"type":"string","title":"Retrieval Mode","description":"Retrieval mode: 'chunked' for vector search or 'full_document' for full text retrieval","default":"chunked"}},"type":"object","required":["document_uuid","s3_key"],"title":"ProcessDocumentRequestSchema","description":"Request schema for triggering document processing."},"PropertyOption":{"properties":{"value":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"boolean"},{"type":"number"}],"title":"Value"},"label":{"type":"string","title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"additionalProperties":false,"type":"object","required":["value","label"],"title":"PropertyOption","description":"An option in an `options` or `multi_options` dropdown."},"PropertySpec":{"properties":{"name":{"type":"string","title":"Name"},"type":{"$ref":"#/components/schemas/PropertyType"},"display_name":{"type":"string","title":"Display Name"},"description":{"type":"string","minLength":1,"title":"Description","description":"Human-facing explanation shown in the UI."},"llm_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Hint","description":"LLM-only guidance; omitted from the UI."},"default":{"title":"Default"},"required":{"type":"boolean","title":"Required","default":false},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder"},"display_options":{"anyOf":[{"$ref":"#/components/schemas/DisplayOptions"},{"type":"null"}]},"options":{"anyOf":[{"items":{"$ref":"#/components/schemas/PropertyOption"},"type":"array"},{"type":"null"}],"title":"Options"},"properties":{"anyOf":[{"items":{"$ref":"#/components/schemas/PropertySpec"},"type":"array"},{"type":"null"}],"title":"Properties"},"min_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Value"},"max_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Value"},"min_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Length"},"max_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Length"},"pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pattern"},"editor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editor"},"extra":{"additionalProperties":true,"type":"object","title":"Extra"}},"additionalProperties":false,"type":"object","required":["name","type","display_name","description"],"title":"PropertySpec","description":"Single field on a node.\n\n`description` is HUMAN-FACING \u2014 shown under the field in the edit\ndialog. Keep it concise and explain what the field does.\n\n`llm_hint` is LLM-FACING \u2014 appears only in the `get_node_type` MCP\nresponse and in SDK schema output. Use it for catalog tool references\n(e.g., \"Use `list_recordings`\"), array shape, expected value idioms,\nor anything that would be noise in the UI. Optional; omit when the\n`description` already suffices for both audiences."},"PropertyType":{"type":"string","enum":["string","number","boolean","options","multi_options","fixed_collection","json","tool_refs","document_refs","recording_ref","credential_ref","mention_textarea","url"],"title":"PropertyType","description":"Bounded vocabulary of property types the renderer dispatches on.\n\nAdding a value here requires a matching arm in the frontend\n`` switch and (where relevant) the SDK codegen template."},"ProviderSyncStatus":{"properties":{"ok":{"type":"boolean","title":"Ok"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["ok"],"title":"ProviderSyncStatus","description":"Result of pushing a phone-number change to the upstream provider.\n\nReturned alongside create/update responses when the route attempted to\nsync inbound webhook configuration. ``ok=False`` is a warning, not a\nfatal error \u2014 the DB write succeeded."},"RecordingCreateRequestSchema":{"properties":{"recording_id":{"type":"string","title":"Recording Id","description":"Short recording ID from upload step"},"tts_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Provider","description":"TTS provider (e.g. elevenlabs)"},"tts_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Model","description":"TTS model name"},"tts_voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Voice Id","description":"TTS voice identifier"},"transcript":{"type":"string","title":"Transcript","description":"User-provided transcript of the recording"},"storage_key":{"type":"string","title":"Storage Key","description":"Storage key from upload step"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Optional metadata (file_size, duration, etc.)"}},"type":"object","required":["recording_id","transcript","storage_key"],"title":"RecordingCreateRequestSchema","description":"Request schema for creating a recording record after upload."},"RecordingListResponseSchema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/RecordingResponseSchema"},"type":"array","title":"Recordings"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["recordings","total"],"title":"RecordingListResponseSchema","description":"Response schema for list of recordings."},"RecordingResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"recording_id":{"type":"string","title":"Recording Id"},"workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Id"},"organization_id":{"type":"integer","title":"Organization Id"},"tts_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Provider"},"tts_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Model"},"tts_voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Voice Id"},"transcript":{"type":"string","title":"Transcript"},"storage_key":{"type":"string","title":"Storage Key"},"storage_backend":{"type":"string","title":"Storage Backend"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"created_by":{"type":"integer","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","recording_id","organization_id","transcript","storage_key","storage_backend","metadata","created_by","created_at","is_active"],"title":"RecordingResponseSchema","description":"Response schema for a single recording."},"RecordingUpdateRequestSchema":{"properties":{"recording_id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","title":"Recording Id","description":"New descriptive recording ID (letters, numbers, hyphens, underscores only)"}},"type":"object","required":["recording_id"],"title":"RecordingUpdateRequestSchema","description":"Request schema for updating a recording's ID."},"RecordingUploadResponseSchema":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Presigned URL for uploading the audio"},"recording_id":{"type":"string","title":"Recording Id","description":"Short unique recording ID"},"storage_key":{"type":"string","title":"Storage Key","description":"Storage key where file will be uploaded"}},"type":"object","required":["upload_url","recording_id","storage_key"],"title":"RecordingUploadResponseSchema","description":"Response schema with presigned upload URL."},"RedialCampaignRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name","description":"Name for the redial campaign"},"retry_on_voicemail":{"type":"boolean","title":"Retry On Voicemail","default":true},"retry_on_no_answer":{"type":"boolean","title":"Retry On No Answer","default":true},"retry_on_busy":{"type":"boolean","title":"Retry On Busy","default":true},"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigRequest"},{"type":"null"}]}},"type":"object","title":"RedialCampaignRequest"},"RetryConfigRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"max_retries":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Max Retries","default":2},"retry_delay_seconds":{"type":"integer","maximum":3600.0,"minimum":30.0,"title":"Retry Delay Seconds","default":120},"retry_on_busy":{"type":"boolean","title":"Retry On Busy","default":true},"retry_on_no_answer":{"type":"boolean","title":"Retry On No Answer","default":true},"retry_on_voicemail":{"type":"boolean","title":"Retry On Voicemail","default":true}},"type":"object","title":"RetryConfigRequest"},"RetryConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"max_retries":{"type":"integer","title":"Max Retries"},"retry_delay_seconds":{"type":"integer","title":"Retry Delay Seconds"},"retry_on_busy":{"type":"boolean","title":"Retry On Busy"},"retry_on_no_answer":{"type":"boolean","title":"Retry On No Answer"},"retry_on_voicemail":{"type":"boolean","title":"Retry On Voicemail"}},"type":"object","required":["enabled","max_retries","retry_delay_seconds","retry_on_busy","retry_on_no_answer","retry_on_voicemail"],"title":"RetryConfigResponse"},"S3SignedUrlResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["url","expires_in"],"title":"S3SignedUrlResponse"},"ScheduleConfigRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"timezone":{"type":"string","title":"Timezone","default":"UTC"},"slots":{"items":{"$ref":"#/components/schemas/TimeSlotRequest"},"type":"array","maxItems":50,"minItems":1,"title":"Slots"}},"type":"object","required":["slots"],"title":"ScheduleConfigRequest"},"ScheduleConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"timezone":{"type":"string","title":"Timezone"},"slots":{"items":{"$ref":"#/components/schemas/TimeSlotResponse"},"type":"array","title":"Slots"}},"type":"object","required":["enabled","timezone","slots"],"title":"ScheduleConfigResponse"},"ServiceKeyResponse":{"properties":{"name":{"type":"string","title":"Name"},"id":{"type":"integer","title":"Id"},"key_prefix":{"type":"string","title":"Key Prefix"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"archived_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Archived At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["name","id","key_prefix","is_active","created_at"],"title":"ServiceKeyResponse"},"SessionResponse":{"properties":{"session_token":{"type":"string","title":"Session Token"},"expires_at":{"type":"string","title":"Expires At"}},"type":"object","required":["session_token","expires_at"],"title":"SessionResponse"},"SignupRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["email","password"],"title":"SignupRequest"},"SuperuserWorkflowRunResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"},"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"organization_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Name"},"mode":{"type":"string","title":"Mode"},"is_completed":{"type":"boolean","title":"Is Completed"},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url"},"transcript_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Url"},"usage_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Usage Info"},"cost_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cost Info"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","workflow_id","workflow_name","user_id","organization_id","organization_name","mode","is_completed","recording_url","transcript_url","usage_info","cost_info","initial_context","gathered_context","created_at"],"title":"SuperuserWorkflowRunResponse"},"SuperuserWorkflowRunsListResponse":{"properties":{"workflow_runs":{"items":{"$ref":"#/components/schemas/SuperuserWorkflowRunResponse"},"type":"array","title":"Workflow Runs"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["workflow_runs","total_count","page","limit","total_pages"],"title":"SuperuserWorkflowRunsListResponse"},"TelephonyConfigWarningsResponse":{"properties":{"telnyx_missing_webhook_public_key_count":{"type":"integer","title":"Telnyx Missing Webhook Public Key Count"}},"type":"object","required":["telnyx_missing_webhook_public_key_count"],"title":"TelephonyConfigWarningsResponse","description":"Aggregated telephony-configuration warning counts for the user's org.\n\nDrives the page banner and nav badge that nudge customers to finish\noptional-but-recommended configuration steps. Shape is a flat dict so\nnew warning types can be added without breaking the client."},"TelephonyConfigurationCreateRequest":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name"},"is_default_outbound":{"type":"boolean","title":"Is Default Outbound","default":false},"config":{"oneOf":[{"$ref":"#/components/schemas/ARIConfigurationRequest"},{"$ref":"#/components/schemas/CloudonixConfigurationRequest"},{"$ref":"#/components/schemas/PlivoConfigurationRequest"},{"$ref":"#/components/schemas/TelnyxConfigurationRequest"},{"$ref":"#/components/schemas/TwilioConfigurationRequest"},{"$ref":"#/components/schemas/VobizConfigurationRequest"},{"$ref":"#/components/schemas/VonageConfigurationRequest"}],"title":"Config","discriminator":{"propertyName":"provider","mapping":{"ari":"#/components/schemas/ARIConfigurationRequest","cloudonix":"#/components/schemas/CloudonixConfigurationRequest","plivo":"#/components/schemas/PlivoConfigurationRequest","telnyx":"#/components/schemas/TelnyxConfigurationRequest","twilio":"#/components/schemas/TwilioConfigurationRequest","vobiz":"#/components/schemas/VobizConfigurationRequest","vonage":"#/components/schemas/VonageConfigurationRequest"}}}},"type":"object","required":["name","config"],"title":"TelephonyConfigurationCreateRequest","description":"Body for ``POST /telephony-configs``.\n\n``config`` carries the provider-specific credential fields (the same\ndiscriminated union used by the legacy single-config endpoint). Any\n``from_numbers`` on the inner config are ignored \u2014 phone numbers are\nmanaged via the dedicated phone-numbers endpoints."},"TelephonyConfigurationDetail":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"provider":{"type":"string","title":"Provider"},"is_default_outbound":{"type":"boolean","title":"Is Default Outbound"},"credentials":{"additionalProperties":true,"type":"object","title":"Credentials"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","provider","is_default_outbound","credentials","created_at","updated_at"],"title":"TelephonyConfigurationDetail","description":"Body of ``GET /telephony-configs/{id}`` \u2014 credentials are masked."},"TelephonyConfigurationListItem":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"provider":{"type":"string","title":"Provider"},"is_default_outbound":{"type":"boolean","title":"Is Default Outbound"},"phone_number_count":{"type":"integer","title":"Phone Number Count","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","provider","is_default_outbound","created_at","updated_at"],"title":"TelephonyConfigurationListItem","description":"One row in ``GET /telephony-configs``."},"TelephonyConfigurationListResponse":{"properties":{"configurations":{"items":{"$ref":"#/components/schemas/TelephonyConfigurationListItem"},"type":"array","title":"Configurations"}},"type":"object","required":["configurations"],"title":"TelephonyConfigurationListResponse"},"TelephonyConfigurationResponse":{"properties":{"twilio":{"anyOf":[{"$ref":"#/components/schemas/TwilioConfigurationResponse"},{"type":"null"}]},"plivo":{"anyOf":[{"$ref":"#/components/schemas/PlivoConfigurationResponse"},{"type":"null"}]},"vonage":{"anyOf":[{"$ref":"#/components/schemas/VonageConfigurationResponse"},{"type":"null"}]},"vobiz":{"anyOf":[{"$ref":"#/components/schemas/VobizConfigurationResponse"},{"type":"null"}]},"cloudonix":{"anyOf":[{"$ref":"#/components/schemas/CloudonixConfigurationResponse"},{"type":"null"}]},"ari":{"anyOf":[{"$ref":"#/components/schemas/ARIConfigurationResponse"},{"type":"null"}]},"telnyx":{"anyOf":[{"$ref":"#/components/schemas/TelnyxConfigurationResponse"},{"type":"null"}]}},"type":"object","title":"TelephonyConfigurationResponse","description":"Top-level telephony configuration response.\n\nKeeps the per-provider field shape that the UI client depends on. When\nthe UI moves to metadata-driven forms, this can be replaced with a\nflat discriminated union."},"TelephonyConfigurationUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Name"},"config":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/ARIConfigurationRequest"},{"$ref":"#/components/schemas/CloudonixConfigurationRequest"},{"$ref":"#/components/schemas/PlivoConfigurationRequest"},{"$ref":"#/components/schemas/TelnyxConfigurationRequest"},{"$ref":"#/components/schemas/TwilioConfigurationRequest"},{"$ref":"#/components/schemas/VobizConfigurationRequest"},{"$ref":"#/components/schemas/VonageConfigurationRequest"}],"discriminator":{"propertyName":"provider","mapping":{"ari":"#/components/schemas/ARIConfigurationRequest","cloudonix":"#/components/schemas/CloudonixConfigurationRequest","plivo":"#/components/schemas/PlivoConfigurationRequest","telnyx":"#/components/schemas/TelnyxConfigurationRequest","twilio":"#/components/schemas/TwilioConfigurationRequest","vobiz":"#/components/schemas/VobizConfigurationRequest","vonage":"#/components/schemas/VonageConfigurationRequest"}}},{"type":"null"}],"title":"Config"}},"type":"object","title":"TelephonyConfigurationUpdateRequest","description":"Body for ``PUT /telephony-configs/{id}``. Partial update."},"TelephonyProviderMetadata":{"properties":{"provider":{"type":"string","title":"Provider"},"display_name":{"type":"string","title":"Display Name"},"fields":{"items":{"$ref":"#/components/schemas/TelephonyProviderUIField"},"type":"array","title":"Fields"},"docs_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Docs Url"}},"type":"object","required":["provider","display_name","fields"],"title":"TelephonyProviderMetadata","description":"UI form metadata for a single telephony provider."},"TelephonyProviderUIField":{"properties":{"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type"},"required":{"type":"boolean","title":"Required"},"sensitive":{"type":"boolean","title":"Sensitive"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder"}},"type":"object","required":["name","label","type","required","sensitive"],"title":"TelephonyProviderUIField","description":"One form field on a telephony provider's configuration UI."},"TelephonyProvidersMetadataResponse":{"properties":{"providers":{"items":{"$ref":"#/components/schemas/TelephonyProviderMetadata"},"type":"array","title":"Providers"}},"type":"object","required":["providers"],"title":"TelephonyProvidersMetadataResponse","description":"List of UI form definitions used by the telephony-config screen."},"TelnyxConfigurationRequest":{"properties":{"provider":{"type":"string","const":"telnyx","title":"Provider","default":"telnyx"},"api_key":{"type":"string","title":"Api Key","description":"Telnyx API Key"},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id","description":"Telnyx Call Control Application ID (connection_id). If omitted, a Call Control Application is auto-created on save and its id is stored on the configuration."},"webhook_public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Public Key","description":"Webhook public key from Mission Control Portal \u2192 Keys & Credentials \u2192 Public Key. Used to verify Telnyx webhook signatures."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Telnyx phone numbers"}},"type":"object","required":["api_key"],"title":"TelnyxConfigurationRequest","description":"Request schema for Telnyx configuration."},"TelnyxConfigurationResponse":{"properties":{"provider":{"type":"string","const":"telnyx","title":"Provider","default":"telnyx"},"api_key":{"type":"string","title":"Api Key"},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id"},"webhook_public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Public Key"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["api_key","from_numbers"],"title":"TelnyxConfigurationResponse","description":"Response schema for Telnyx configuration with masked sensitive fields."},"TimeSlotRequest":{"properties":{"day_of_week":{"type":"integer","maximum":6.0,"minimum":0.0,"title":"Day Of Week"},"start_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","title":"Start Time"},"end_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","title":"End Time"}},"type":"object","required":["day_of_week","start_time","end_time"],"title":"TimeSlotRequest"},"TimeSlotResponse":{"properties":{"day_of_week":{"type":"integer","title":"Day Of Week"},"start_time":{"type":"string","title":"Start Time"},"end_time":{"type":"string","title":"End Time"}},"type":"object","required":["day_of_week","start_time","end_time"],"title":"TimeSlotResponse"},"ToolParameter":{"properties":{"name":{"type":"string","title":"Name","description":"Parameter name (used as key in request body)"},"type":{"type":"string","title":"Type","description":"Parameter type: string, number, or boolean"},"description":{"type":"string","title":"Description","description":"Description of what this parameter is for"},"required":{"type":"boolean","title":"Required","description":"Whether this parameter is required","default":true}},"type":"object","required":["name","type","description"],"title":"ToolParameter","description":"A parameter that the tool accepts."},"ToolResponse":{"properties":{"id":{"type":"integer","title":"Id"},"tool_uuid":{"type":"string","title":"Tool Uuid"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"type":"string","title":"Category"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"icon_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon Color"},"status":{"type":"string","title":"Status"},"definition":{"additionalProperties":true,"type":"object","title":"Definition"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"created_by":{"anyOf":[{"$ref":"#/components/schemas/CreatedByResponse"},{"type":"null"}]}},"type":"object","required":["id","tool_uuid","name","description","category","icon","icon_color","status","definition","created_at","updated_at"],"title":"ToolResponse","description":"Response schema for a tool."},"TransferCallConfig":{"properties":{"destination":{"type":"string","title":"Destination","description":"Phone number or SIP endpoint to transfer the call to (E.164 format e.g., +1234567890, or SIP endpoint e.g., PJSIP/1234)"},"messageType":{"type":"string","enum":["none","custom","audio"],"title":"Messagetype","description":"Type of message to play before transfer","default":"none"},"customMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessage","description":"Custom message to play before transferring the call"},"audioRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audiorecordingid","description":"Recording ID for audio message before transfer"},"timeout":{"type":"integer","maximum":120.0,"minimum":5.0,"title":"Timeout","description":"Maximum time in seconds to wait for destination to answer (5-120 seconds)","default":30}},"type":"object","required":["destination"],"title":"TransferCallConfig","description":"Configuration for Transfer Call tools."},"TransferCallToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"transfer_call","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/TransferCallConfig","description":"Transfer Call configuration"}},"type":"object","required":["type","config"],"title":"TransferCallToolDefinition","description":"Tool definition for Transfer Call tools."},"TriggerCallRequest":{"properties":{"phone_number":{"type":"string","title":"Phone Number"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"}},"type":"object","required":["phone_number"],"title":"TriggerCallRequest","description":"Request model for triggering a call via API"},"TriggerCallResponse":{"properties":{"status":{"type":"string","title":"Status"},"workflow_run_id":{"type":"integer","title":"Workflow Run Id"},"workflow_run_name":{"type":"string","title":"Workflow Run Name"}},"type":"object","required":["status","workflow_run_id","workflow_run_name"],"title":"TriggerCallResponse","description":"Response model for successful call initiation"},"TurnCredentialsResponse":{"properties":{"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"ttl":{"type":"integer","title":"Ttl"},"uris":{"items":{"type":"string"},"type":"array","title":"Uris"}},"type":"object","required":["username","password","ttl","uris"],"title":"TurnCredentialsResponse","description":"Response model for TURN credentials."},"TwilioConfigurationRequest":{"properties":{"provider":{"type":"string","const":"twilio","title":"Provider","default":"twilio"},"account_sid":{"type":"string","title":"Account Sid","description":"Twilio Account SID"},"auth_token":{"type":"string","title":"Auth Token","description":"Twilio Auth Token"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Twilio phone numbers"}},"type":"object","required":["account_sid","auth_token"],"title":"TwilioConfigurationRequest","description":"Request schema for Twilio configuration."},"TwilioConfigurationResponse":{"properties":{"provider":{"type":"string","const":"twilio","title":"Provider","default":"twilio"},"account_sid":{"type":"string","title":"Account Sid"},"auth_token":{"type":"string","title":"Auth Token"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["account_sid","auth_token","from_numbers"],"title":"TwilioConfigurationResponse","description":"Response schema for Twilio configuration with masked sensitive fields."},"UpdateCampaignRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigRequest"},{"type":"null"}]},"max_concurrency":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":1.0},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigRequest"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigRequest"},{"type":"null"}]}},"type":"object","title":"UpdateCampaignRequest"},"UpdateCredentialRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"credential_type":{"anyOf":[{"$ref":"#/components/schemas/WebhookCredentialType"},{"type":"null"}]},"credential_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Credential Data"}},"type":"object","title":"UpdateCredentialRequest","description":"Request schema for updating a webhook credential."},"UpdateIntegrationRequest":{"properties":{"selected_files":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Selected Files"}},"type":"object","required":["selected_files"],"title":"UpdateIntegrationRequest"},"UpdateToolRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"icon":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Icon"},"icon_color":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Icon Color"},"definition":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/HttpApiToolDefinition"},{"$ref":"#/components/schemas/EndCallToolDefinition"},{"$ref":"#/components/schemas/TransferCallToolDefinition"},{"$ref":"#/components/schemas/CalculatorToolDefinition"},{"$ref":"#/components/schemas/McpToolDefinition"}],"discriminator":{"propertyName":"type","mapping":{"calculator":"#/components/schemas/CalculatorToolDefinition","end_call":"#/components/schemas/EndCallToolDefinition","http_api":"#/components/schemas/HttpApiToolDefinition","mcp":"#/components/schemas/McpToolDefinition","transfer_call":"#/components/schemas/TransferCallToolDefinition"}}},{"type":"null"}],"title":"Definition"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"UpdateToolRequest","description":"Request schema for updating a tool."},"UpdateWorkflowRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"workflow_definition":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Definition"},"template_context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Context Variables"},"workflow_configurations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Configurations"}},"type":"object","title":"UpdateWorkflowRequest"},"UpdateWorkflowStatusRequest":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"UpdateWorkflowStatusRequest"},"UsageHistoryResponse":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/WorkflowRunUsageResponse"},"type":"array","title":"Runs"},"total_dograh_tokens":{"type":"number","title":"Total Dograh Tokens"},"total_duration_seconds":{"type":"integer","title":"Total Duration Seconds"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["runs","total_dograh_tokens","total_duration_seconds","total_count","page","limit","total_pages"],"title":"UsageHistoryResponse"},"UserConfigurationRequestResponseSchema":{"properties":{"llm":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Llm"},"tts":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Tts"},"stt":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Stt"},"embeddings":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Embeddings"},"realtime":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Realtime"},"is_realtime":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Realtime"},"test_phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Phone Number"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"organization_pricing":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"boolean"}]},"type":"object"},{"type":"null"}],"title":"Organization Pricing"}},"type":"object","title":"UserConfigurationRequestResponseSchema"},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},"type":"object","required":["id","email"],"title":"UserResponse"},"ValidateWorkflowResponse":{"properties":{"is_valid":{"type":"boolean","title":"Is Valid"},"errors":{"items":{"$ref":"#/components/schemas/WorkflowError"},"type":"array","title":"Errors"}},"type":"object","required":["is_valid","errors"],"title":"ValidateWorkflowResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VobizConfigurationRequest":{"properties":{"provider":{"type":"string","const":"vobiz","title":"Provider","default":"vobiz"},"auth_id":{"type":"string","title":"Auth Id","description":"Vobiz Account ID (e.g., MA_SYQRLN1K)"},"auth_token":{"type":"string","title":"Auth Token","description":"Vobiz Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id","description":"Vobiz Application ID. The application's answer_url is updated when inbound workflows are attached to numbers on this account. If omitted, an application is auto-created on save and its id is stored on the configuration."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Vobiz phone numbers (E.164 without + prefix)"}},"type":"object","required":["auth_id","auth_token"],"title":"VobizConfigurationRequest","description":"Request schema for Vobiz configuration."},"VobizConfigurationResponse":{"properties":{"provider":{"type":"string","const":"vobiz","title":"Provider","default":"vobiz"},"auth_id":{"type":"string","title":"Auth Id"},"auth_token":{"type":"string","title":"Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["auth_id","auth_token","from_numbers"],"title":"VobizConfigurationResponse","description":"Response schema for Vobiz configuration with masked sensitive fields."},"VoiceInfo":{"properties":{"voice_id":{"type":"string","title":"Voice Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"accent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url"}},"type":"object","required":["voice_id","name"],"title":"VoiceInfo"},"VoicesResponse":{"properties":{"provider":{"type":"string","title":"Provider"},"voices":{"items":{"$ref":"#/components/schemas/VoiceInfo"},"type":"array","title":"Voices"}},"type":"object","required":["provider","voices"],"title":"VoicesResponse"},"VonageConfigurationRequest":{"properties":{"provider":{"type":"string","const":"vonage","title":"Provider","default":"vonage"},"api_key":{"type":"string","title":"Api Key","description":"Vonage API Key"},"api_secret":{"type":"string","title":"Api Secret","description":"Vonage API Secret"},"application_id":{"type":"string","title":"Application Id","description":"Vonage Application ID"},"private_key":{"type":"string","title":"Private Key","description":"Private key for JWT generation"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Vonage phone numbers (without + prefix)"}},"type":"object","required":["api_key","api_secret","application_id","private_key"],"title":"VonageConfigurationRequest","description":"Request schema for Vonage configuration."},"VonageConfigurationResponse":{"properties":{"provider":{"type":"string","const":"vonage","title":"Provider","default":"vonage"},"application_id":{"type":"string","title":"Application Id"},"api_key":{"type":"string","title":"Api Key"},"api_secret":{"type":"string","title":"Api Secret"},"private_key":{"type":"string","title":"Private Key"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["application_id","api_key","api_secret","private_key","from_numbers"],"title":"VonageConfigurationResponse","description":"Response schema for Vonage configuration with masked sensitive fields."},"WebhookCredentialType":{"type":"string","enum":["none","api_key","bearer_token","basic_auth","custom_header"],"title":"WebhookCredentialType","description":"Webhook credential authentication types"},"WorkflowCountResponse":{"properties":{"total":{"type":"integer","title":"Total"},"active":{"type":"integer","title":"Active"},"archived":{"type":"integer","title":"Archived"}},"type":"object","required":["total","active","archived"],"title":"WorkflowCountResponse","description":"Response for workflow count endpoint."},"WorkflowError":{"properties":{"kind":{"$ref":"#/components/schemas/ItemKind"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field"},"message":{"type":"string","title":"Message"}},"type":"object","required":["kind","id","field","message"],"title":"WorkflowError"},"WorkflowListResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"total_runs":{"type":"integer","title":"Total Runs"}},"type":"object","required":["id","name","status","created_at","total_runs"],"title":"WorkflowListResponse","description":"Lightweight response for workflow listings (excludes large fields)."},"WorkflowOption":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"WorkflowOption"},"WorkflowResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"workflow_definition":{"additionalProperties":true,"type":"object","title":"Workflow Definition"},"current_definition_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Current Definition Id"},"template_context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Context Variables"},"call_disposition_codes":{"anyOf":[{"$ref":"#/components/schemas/CallDispositionCodes"},{"type":"null"}]},"total_runs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Runs"},"workflow_configurations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Configurations"},"version_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Version Number"},"version_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version Status"},"workflow_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Uuid"}},"type":"object","required":["id","name","status","created_at","workflow_definition","current_definition_id"],"title":"WorkflowResponse"},"WorkflowRunDetail":{"properties":{"phone_number":{"type":"string","title":"Phone Number"},"disposition":{"type":"string","title":"Disposition"},"duration_seconds":{"type":"number","title":"Duration Seconds"},"workflow_id":{"type":"integer","title":"Workflow Id"},"run_id":{"type":"integer","title":"Run Id"},"workflow_name":{"type":"string","title":"Workflow Name"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["phone_number","disposition","duration_seconds","workflow_id","run_id","workflow_name","created_at"],"title":"WorkflowRunDetail"},"WorkflowRunResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"name":{"type":"string","title":"Name"},"mode":{"type":"string","title":"Mode"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_completed":{"type":"boolean","title":"Is Completed"},"transcript_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Url"},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url"},"cost_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cost Info"},"definition_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Definition Id"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"call_type":{"$ref":"#/components/schemas/CallType"},"logs":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Logs"},"annotations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annotations"}},"type":"object","required":["id","workflow_id","name","mode","created_at","is_completed","transcript_url","recording_url","cost_info","definition_id","call_type"],"title":"WorkflowRunResponseSchema"},"WorkflowRunUsageResponse":{"properties":{"id":{"type":"integer","title":"Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","title":"Created At"},"dograh_token_usage":{"type":"number","title":"Dograh Token Usage"},"call_duration_seconds":{"type":"integer","title":"Call Duration Seconds"},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url"},"transcript_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Url"},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number","description":"Deprecated. Use caller_number and called_number instead.","deprecated":true},"caller_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caller Number"},"called_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Called Number"},"call_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Call Type"},"disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"charge_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Charge Usd"}},"type":"object","required":["id","workflow_id","workflow_name","name","created_at","dograh_token_usage","call_duration_seconds"],"title":"WorkflowRunUsageResponse"},"WorkflowRunsResponse":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/WorkflowRunResponseSchema"},"type":"array","title":"Runs"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"},"applied_filters":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Applied Filters"}},"type":"object","required":["runs","total_count","page","limit","total_pages"],"title":"WorkflowRunsResponse"},"WorkflowSummaryResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"WorkflowSummaryResponse"},"WorkflowTemplateResponse":{"properties":{"id":{"type":"integer","title":"Id"},"template_name":{"type":"string","title":"Template Name"},"template_description":{"type":"string","title":"Template Description"},"template_json":{"additionalProperties":true,"type":"object","title":"Template Json"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","template_name","template_description","template_json","created_at"],"title":"WorkflowTemplateResponse"},"WorkflowVersionResponse":{"properties":{"id":{"type":"integer","title":"Id"},"version_number":{"type":"integer","title":"Version Number"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At"},"workflow_json":{"additionalProperties":true,"type":"object","title":"Workflow Json"},"workflow_configurations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Configurations"},"template_context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Context Variables"}},"type":"object","required":["id","version_number","status","created_at","workflow_json"],"title":"WorkflowVersionResponse"}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"Dograh API","description":"API for the Dograh app","version":"1.0.0"},"servers":[{"url":"https://app.dograh.com","description":"Production"},{"url":"http://localhost:8000","description":"Local development"}],"paths":{"/api/v1/telephony/initiate-call":{"post":{"tags":["main"],"summary":"Initiate Call","description":"Initiate a call using the configured telephony provider from web browser. This is\nsupposed to be a test call method for the draft version of the agent.","operationId":"initiate_call_api_v1_telephony_initiate_call_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiateCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"test_phone_call","x-sdk-description":"Place a test call from a workflow to a phone number."}},"/api/v1/telephony/inbound/run":{"post":{"tags":["main"],"summary":"Handle Inbound Run","description":"Workflow-agnostic inbound dispatcher.\n\nAll providers can point a single webhook at this endpoint instead of one\nURL per workflow. The dispatcher resolves the org from the webhook's\naccount_id and the workflow from the called number's\n``inbound_workflow_id``. This is what ``configure_inbound`` writes into\neach provider's resource so per-workflow webhook bookkeeping disappears.\n\nProvider-specific signature/timestamp headers are not enumerated here \u2014\neach provider's ``verify_inbound_signature`` reads its own headers from\nthe dict, so adding a new provider doesn't require changes to this route.","operationId":"handle_inbound_run_api_v1_telephony_inbound_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/telephony/inbound/fallback":{"post":{"tags":["main"],"summary":"Handle Inbound Fallback","description":"Fallback endpoint that returns audio message when calls cannot be processed.","operationId":"handle_inbound_fallback_api_v1_telephony_inbound_fallback_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/telephony/inbound/{workflow_id}":{"post":{"tags":["main"],"summary":"Handle Inbound Telephony","description":"[LEGACY] Per-workflow inbound webhook.\n\nSuperseded by ``POST /inbound/run``, which resolves the workflow from\nthe called number's ``inbound_workflow_id`` and lets a single webhook\nURL serve every workflow in the org. New integrations should point\ntheir provider at ``/inbound/run``; this route is kept only for\nexisting provider configurations that still encode ``workflow_id``\nin the URL.","operationId":"handle_inbound_telephony_api_v1_telephony_inbound__workflow_id__post","deprecated":true,"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/transfer-result/{transfer_id}":{"post":{"tags":["main"],"summary":"Complete Transfer Function Call","description":"Webhook endpoint to complete the function call with transfer result.\n\nCalled by Twilio's StatusCallback when the transfer call status changes.","operationId":"complete_transfer_function_call_api_v1_telephony_transfer_result__transfer_id__post","parameters":[{"name":"transfer_id","in":"path","required":true,"schema":{"type":"string","title":"Transfer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/cloudonix/status-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Cloudonix Status Callback","description":"Handle Cloudonix-specific status callbacks.\n\nCloudonix sends call status updates to the callback URL specified during call initiation.","operationId":"handle_cloudonix_status_callback_api_v1_telephony_cloudonix_status_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/cloudonix/cdr":{"post":{"tags":["main"],"summary":"Handle Cloudonix Cdr","description":"Handle Cloudonix CDR (Call Detail Record) webhooks.\n\nCloudonix sends CDR records when calls complete. The CDR contains:\n- domain: Used to identify the organization\n- call_id: Used to find the workflow run\n- disposition: Call termination status (ANSWER, BUSY, CANCEL, FAILED, CONGESTION, NOANSWER)\n- duration/billsec: Call duration information","operationId":"handle_cloudonix_cdr_api_v1_telephony_cloudonix_cdr_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/telephony/plivo/hangup-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Plivo Hangup Callback","description":"Handle Plivo hangup callbacks.","operationId":"handle_plivo_hangup_callback_api_v1_telephony_plivo_hangup_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-plivo-signature-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3"}},{"name":"x-plivo-signature-ma-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-Ma-V3"}},{"name":"x-plivo-signature-v3-nonce","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3-Nonce"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/plivo/ring-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Plivo Ring Callback","description":"Handle Plivo ring callbacks.","operationId":"handle_plivo_ring_callback_api_v1_telephony_plivo_ring_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-plivo-signature-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3"}},{"name":"x-plivo-signature-ma-v3","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-Ma-V3"}},{"name":"x-plivo-signature-v3-nonce","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Plivo-Signature-V3-Nonce"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/telnyx/events/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Telnyx Events","description":"Handle Telnyx Call Control webhook events.\n\nTelnyx sends all call lifecycle events (call.initiated, call.answered,\ncall.hangup, streaming.started, streaming.stopped) as JSON POST requests.","operationId":"handle_telnyx_events_api_v1_telephony_telnyx_events__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/telnyx/transfer-result/{transfer_id}":{"post":{"tags":["main"],"summary":"Handle Telnyx Transfer Result","description":"Handle Telnyx Call Control events for the transfer destination leg.\n\nThe destination leg is dialed by :meth:`TelnyxProvider.transfer_call` with\nthis URL as ``webhook_url``. Telnyx sends every event for that leg here.\nOutcomes:\n\n- ``call.answered``: seed a conference with the destination's live\n ``call_control_id``, stamp ``conference_id`` onto the TransferContext,\n and publish ``DESTINATION_ANSWERED`` so ``transfer_call_handler`` can\n end the pipeline. ``TelnyxConferenceStrategy`` then joins the caller\n into this conference at pipeline teardown.\n- ``call.hangup`` pre-answer (no ``conference_id`` on the context):\n publish ``TRANSFER_FAILED`` so the LLM can recover.\n- ``call.hangup`` post-answer (``conference_id`` set): the destination\n left a bridged conference; hang up the caller's leg to tear down the\n empty bridge (Telnyx's create_conference doesn't accept\n ``end_conference_on_exit`` on the seed leg).\n\nEvent references:\n - call.answered: https://developers.telnyx.com/api-reference/callbacks/call-answered\n - call.hangup: https://developers.telnyx.com/api-reference/callbacks/call-hangup","operationId":"handle_telnyx_transfer_result_api_v1_telephony_telnyx_transfer_result__transfer_id__post","parameters":[{"name":"transfer_id","in":"path","required":true,"schema":{"type":"string","title":"Transfer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/twilio/status-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Twilio Status Callback","description":"Handle Twilio-specific status callbacks.","operationId":"handle_twilio_status_callback_api_v1_telephony_twilio_status_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-webhook-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Webhook-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vobiz/hangup-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Vobiz Hangup Callback","description":"Handle Vobiz hangup callback (sent when call ends).\n\nVobiz sends callbacks to hangup_url when the call terminates.\nThis includes call duration, status, and billing information.","operationId":"handle_vobiz_hangup_callback_api_v1_telephony_vobiz_hangup_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-vobiz-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Signature"}},{"name":"x-vobiz-timestamp","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Timestamp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vobiz/ring-callback/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Vobiz Ring Callback","description":"Handle Vobiz ring callback (sent when call starts ringing).\n\nVobiz can send callbacks to ring_url when the call starts ringing.\nThis is optional and used for tracking ringing status.","operationId":"handle_vobiz_ring_callback_api_v1_telephony_vobiz_ring_callback__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}},{"name":"x-vobiz-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Signature"}},{"name":"x-vobiz-timestamp","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Timestamp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vobiz/hangup-callback/workflow/{workflow_id}":{"post":{"tags":["main"],"summary":"Handle Vobiz Hangup Callback By Workflow","description":"Handle Vobiz hangup callback with workflow_id - finds workflow run by call_id.","operationId":"handle_vobiz_hangup_callback_by_workflow_api_v1_telephony_vobiz_hangup_callback_workflow__workflow_id__post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"x-vobiz-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Signature"}},{"name":"x-vobiz-timestamp","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Vobiz-Timestamp"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/telephony/vonage/events/{workflow_run_id}":{"post":{"tags":["main"],"summary":"Handle Vonage Events","description":"Handle Vonage-specific event webhooks.\n\nVonage sends all call events to a single endpoint.\nEvents include: started, ringing, answered, complete, failed, etc.","operationId":"handle_vonage_events_api_v1_telephony_vonage_events__workflow_run_id__post","parameters":[{"name":"workflow_run_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/superuser/impersonate":{"post":{"tags":["main","superuser"],"summary":"Impersonate","description":"Impersonate a user as a super-admin.\nInternally, Stack Auth requires the **provider user ID** (a UUID-ish string)\nto create an impersonation session.","operationId":"impersonate_api_v1_superuser_impersonate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpersonateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpersonateResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/superuser/workflow-runs":{"get":{"tags":["main","superuser"],"summary":"Get Workflow Runs","description":"Get paginated list of all workflow runs with organization information.\nRequires superuser privileges.\n\nFilters should be provided as a JSON-encoded array of filter criteria.\nExample: [{\"field\": \"id\", \"type\": \"number\", \"value\": {\"value\": 680}}]","operationId":"get_workflow_runs_api_v1_superuser_workflow_runs_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number (starts from 1)","default":1,"title":"Page"},"description":"Page number (starts from 1)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of items per page","default":50,"title":"Limit"},"description":"Number of items per page"},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded filter criteria","title":"Filters"},"description":"JSON-encoded filter criteria"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Field to sort by (e.g., 'duration', 'created_at')","title":"Sort By"},"description":"Field to sort by (e.g., 'duration', 'created_at')"},{"name":"sort_order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort order ('asc' or 'desc')","default":"desc","title":"Sort Order"},"description":"Sort order ('asc' or 'desc')"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuperuserWorkflowRunsListResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/validate":{"post":{"tags":["main"],"summary":"Validate Workflow","description":"Validate all nodes in a workflow to ensure they have required fields.\n\nArgs:\n workflow_id: The ID of the workflow to validate\n user: The authenticated user\n\nReturns:\n Object indicating if workflow is valid and any invalid nodes/edges","operationId":"validate_workflow_api_v1_workflow__workflow_id__validate_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateWorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/create/definition":{"post":{"tags":["main"],"summary":"Create Workflow","description":"Create a new workflow from the client\n\nArgs:\n request: The create workflow request\n user: The user to create the workflow for","operationId":"create_workflow_api_v1_workflow_create_definition_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"create_workflow","x-sdk-description":"Create a new workflow from a workflow definition."}},"/api/v1/workflow/create/template":{"post":{"tags":["main"],"summary":"Create Workflow From Template","description":"Create a new workflow from a natural language template request.\n\nThis endpoint:\n1. Uses mps_service_key_client to call MPS workflow API\n2. Passes organization ID (authenticated mode) or created_by (OSS mode)\n3. Creates the workflow in the database\n\nArgs:\n request: The template creation request with call_type, use_case, and activity_description\n user: The authenticated user\n\nReturns:\n The created workflow\n\nRaises:\n HTTPException: If MPS API call fails","operationId":"create_workflow_from_template_api_v1_workflow_create_template_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/count":{"get":{"tags":["main"],"summary":"Get Workflow Count","description":"Get workflow counts for the authenticated user's organization.\n\nThis is a lightweight endpoint for checking if the user has workflows,\nuseful for redirect logic without fetching full workflow data.","operationId":"get_workflow_count_api_v1_workflow_count_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowCountResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/fetch":{"get":{"tags":["main"],"summary":"Get Workflows","description":"Get all workflows for the authenticated user's organization.\n\nReturns a lightweight response with only essential fields for listing.\nUse GET /workflow/fetch/{workflow_id} to get full workflow details.","operationId":"get_workflows_api_v1_workflow_fetch_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status - can be single value (active/archived) or comma-separated (active,archived)","title":"Status"},"description":"Filter by status - can be single value (active/archived) or comma-separated (active,archived)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowListResponse"},"title":"Response Get Workflows Api V1 Workflow Fetch Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_workflows","x-sdk-description":"List all workflows in the authenticated organization."}},"/api/v1/workflow/fetch/{workflow_id}":{"get":{"tags":["main"],"summary":"Get Workflow","description":"Get a single workflow by ID.\n\nIf a draft version exists, returns the draft content for editing.\nOtherwise returns the published version's content.","operationId":"get_workflow_api_v1_workflow_fetch__workflow_id__get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"get_workflow","x-sdk-description":"Get a single workflow by ID (returns draft if one exists, else published)."}},"/api/v1/workflow/{workflow_id}/versions":{"get":{"tags":["main"],"summary":"Get Workflow Versions","description":"List versions for a workflow, newest first.\n\nPass `limit`/`offset` to page through long histories. With no `limit`,\nreturns every version (legacy behavior).","operationId":"get_workflow_versions_api_v1_workflow__workflow_id__versions_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowVersionResponse"},"title":"Response Get Workflow Versions Api V1 Workflow Workflow Id Versions Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/publish":{"post":{"tags":["main"],"summary":"Publish Workflow","description":"Publish the current draft version of a workflow.\n\nDrafts are allowed to be incomplete (so the editor can save mid-edit),\nbut a published version is what runtime executes \u2014 so this is the gate\nwhere the full DTO + graph + trigger-conflict checks must pass.","operationId":"publish_workflow_api_v1_workflow__workflow_id__publish_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/create-draft":{"post":{"tags":["main"],"summary":"Create Workflow Draft","description":"Create a draft version from the current published version.\n\nIf a draft already exists, returns the existing draft.","operationId":"create_workflow_draft_api_v1_workflow__workflow_id__create_draft_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowVersionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/summary":{"get":{"tags":["main"],"summary":"Get Workflows Summary","description":"Get minimal workflow information (id and name only) for all workflows","operationId":"get_workflows_summary_api_v1_workflow_summary_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status (e.g. 'active' or 'archived'). Omit to return all.","title":"Status"},"description":"Filter by status (e.g. 'active' or 'archived'). Omit to return all."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSummaryResponse"},"title":"Response Get Workflows Summary Api V1 Workflow Summary Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/status":{"put":{"tags":["main"],"summary":"Update Workflow Status","description":"Update the status of a workflow (e.g., archive/unarchive).\n\nArgs:\n workflow_id: The ID of the workflow to update\n request: The status update request\n\nReturns:\n The updated workflow","operationId":"update_workflow_status_api_v1_workflow__workflow_id__status_put","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkflowStatusRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}":{"put":{"tags":["main"],"summary":"Update Workflow","description":"Update an existing workflow.\n\nArgs:\n workflow_id: The ID of the workflow to update\n request: The update request containing the new name and workflow definition\n\nReturns:\n The updated workflow\n\nRaises:\n HTTPException: If the workflow is not found or if there's a database error","operationId":"update_workflow_api_v1_workflow__workflow_id__put","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkflowRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"update_workflow","x-sdk-description":"Update a workflow's name and/or definition. Saves as a new draft."}},"/api/v1/workflow/{workflow_id}/duplicate":{"post":{"tags":["main"],"summary":"Duplicate Workflow Endpoint","description":"Duplicate a workflow including its definition, configuration, recordings, and triggers.","operationId":"duplicate_workflow_endpoint_api_v1_workflow__workflow_id__duplicate_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/runs":{"post":{"tags":["main"],"summary":"Create Workflow Run","description":"Create a new workflow run when the user decides to execute the workflow via chat or voice\n\nArgs:\n workflow_id: The ID of the workflow to run\n request: The create workflow run request\n user: The user to create the workflow run for","operationId":"create_workflow_run_api_v1_workflow__workflow_id__runs_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowRunResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["main"],"summary":"Get Workflow Runs","description":"Get workflow runs with optional filtering and sorting.\n\nFilters should be provided as a JSON-encoded array of filter criteria.\nExample: [{\"attribute\": \"dateRange\", \"value\": {\"from\": \"2024-01-01\", \"to\": \"2024-01-31\"}}]","operationId":"get_workflow_runs_api_v1_workflow__workflow_id__runs_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded filter criteria","title":"Filters"},"description":"JSON-encoded filter criteria"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Field to sort by (e.g., 'duration', 'created_at')","title":"Sort By"},"description":"Field to sort by (e.g., 'duration', 'created_at')"},{"name":"sort_order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort order ('asc' or 'desc')","default":"desc","title":"Sort Order"},"description":"Sort order ('asc' or 'desc')"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/runs/{run_id}":{"get":{"tags":["main"],"summary":"Get Workflow Run","operationId":"get_workflow_run_api_v1_workflow__workflow_id__runs__run_id__get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"integer","title":"Run Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/report":{"get":{"tags":["main"],"summary":"Download Workflow Report","description":"Download a CSV report of completed runs for a workflow.","operationId":"download_workflow_report_api_v1_workflow__workflow_id__report_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or after this datetime (ISO 8601)","title":"Start Date"},"description":"Filter runs created on or after this datetime (ISO 8601)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or before this datetime (ISO 8601)","title":"End Date"},"description":"Filter runs created on or before this datetime (ISO 8601)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/templates":{"get":{"tags":["main"],"summary":"Get Workflow Templates","description":"Get all available workflow templates.\n\nReturns:\n List of workflow templates","operationId":"get_workflow_templates_api_v1_workflow_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/WorkflowTemplateResponse"},"type":"array","title":"Response Get Workflow Templates Api V1 Workflow Templates Get"}}}},"404":{"description":"Not found"}}}},"/api/v1/workflow/templates/duplicate":{"post":{"tags":["main"],"summary":"Duplicate Workflow Template","description":"Duplicate a workflow template to create a new workflow for the user.\n\nArgs:\n request: The duplicate template request\n user: The authenticated user\n\nReturns:\n The newly created workflow","operationId":"duplicate_workflow_template_api_v1_workflow_templates_duplicate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/ambient-noise/upload-url":{"post":{"tags":["main"],"summary":"Get a presigned URL to upload a custom ambient noise audio file","description":"Generate a presigned PUT URL for uploading a custom ambient noise file.","operationId":"get_ambient_noise_upload_url_api_v1_workflow_ambient_noise_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AmbientNoiseUploadRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AmbientNoiseUploadResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/defaults":{"get":{"tags":["main"],"summary":"Get Default Configurations","operationId":"get_default_configurations_api_v1_user_configurations_defaults_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DefaultConfigurationsResponse"}}}},"404":{"description":"Not found"}}}},"/api/v1/user/auth/user":{"get":{"tags":["main"],"summary":"Get Auth User","operationId":"get_auth_user_api_v1_user_auth_user_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthUserResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/user":{"get":{"tags":["main"],"summary":"Get User Configurations","operationId":"get_user_configurations_api_v1_user_configurations_user_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConfigurationRequestResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update User Configurations","operationId":"update_user_configurations_api_v1_user_configurations_user_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConfigurationRequestResponseSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConfigurationRequestResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/user/validate":{"get":{"tags":["main"],"summary":"Validate User Configurations","operationId":"validate_user_configurations_api_v1_user_configurations_user_validate_get","parameters":[{"name":"validity_ttl_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":86400,"minimum":0,"default":60,"title":"Validity Ttl Seconds"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyStatusResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/api-keys":{"get":{"tags":["main"],"summary":"Get Api Keys","description":"Get all API keys for the user's selected organization.","operationId":"get_api_keys_api_v1_user_api_keys_get","parameters":[{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Archived"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/APIKeyResponse"},"title":"Response Get Api Keys Api V1 User Api Keys Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main"],"summary":"Create Api Key","description":"Create a new API key for the user's selected organization.","operationId":"create_api_key_api_v1_user_api_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/api-keys/{api_key_id}":{"delete":{"tags":["main"],"summary":"Archive Api Key","description":"Archive an API key (soft delete).","operationId":"archive_api_key_api_v1_user_api_keys__api_key_id__delete","parameters":[{"name":"api_key_id","in":"path","required":true,"schema":{"type":"integer","title":"Api Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Archive Api Key Api V1 User Api Keys Api Key Id Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/api-keys/{api_key_id}/reactivate":{"put":{"tags":["main"],"summary":"Reactivate Api Key","description":"Reactivate an archived API key.","operationId":"reactivate_api_key_api_v1_user_api_keys__api_key_id__reactivate_put","parameters":[{"name":"api_key_id","in":"path","required":true,"schema":{"type":"integer","title":"Api Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reactivate Api Key Api V1 User Api Keys Api Key Id Reactivate Put"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/configurations/voices/{provider}":{"get":{"tags":["main"],"summary":"Get Voices","description":"Get available voices for a TTS provider.","operationId":"get_voices_api_v1_user_configurations_voices__provider__get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"enum":["elevenlabs","deepgram","sarvam","cartesia","dograh","rime"],"type":"string","title":"Provider"}},{"name":"model","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"}},{"name":"language","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoicesResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/create":{"post":{"tags":["main"],"summary":"Create Campaign","description":"Create a new campaign","operationId":"create_campaign_api_v1_campaign_create_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/":{"get":{"tags":["main"],"summary":"Get Campaigns","description":"Get campaigns for user's organization","operationId":"get_campaigns_api_v1_campaign__get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}":{"get":{"tags":["main"],"summary":"Get Campaign","description":"Get campaign details","operationId":"get_campaign_api_v1_campaign__campaign_id__get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["main"],"summary":"Update Campaign","description":"Update campaign settings (name, retry config, max concurrency, schedule)","operationId":"update_campaign_api_v1_campaign__campaign_id__patch","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/start":{"post":{"tags":["main"],"summary":"Start Campaign","description":"Start campaign execution","operationId":"start_campaign_api_v1_campaign__campaign_id__start_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/pause":{"post":{"tags":["main"],"summary":"Pause Campaign","description":"Pause campaign execution","operationId":"pause_campaign_api_v1_campaign__campaign_id__pause_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/runs":{"get":{"tags":["main"],"summary":"Get Campaign Runs","description":"Get campaign workflow runs with pagination, filters and sorting","operationId":"get_campaign_runs_api_v1_campaign__campaign_id__runs_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded filter criteria","title":"Filters"},"description":"JSON-encoded filter criteria"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Field to sort by (e.g., 'duration', 'created_at')","title":"Sort By"},"description":"Field to sort by (e.g., 'duration', 'created_at')"},{"name":"sort_order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sort order ('asc' or 'desc')","default":"desc","title":"Sort Order"},"description":"Sort order ('asc' or 'desc')"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignRunsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/redial":{"post":{"tags":["main"],"summary":"Redial Campaign","description":"Create a new campaign that re-dials unique subscribers from a completed\ncampaign whose latest call resulted in voicemail, no-answer, or busy.\n\nThe new campaign is created in 'created' state with queued_runs pre-seeded\nfrom the parent's original initial contexts. A campaign can be redialed at\nmost once.","operationId":"redial_campaign_api_v1_campaign__campaign_id__redial_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedialCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/resume":{"post":{"tags":["main"],"summary":"Resume Campaign","description":"Resume a paused campaign","operationId":"resume_campaign_api_v1_campaign__campaign_id__resume_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/progress":{"get":{"tags":["main"],"summary":"Get Campaign Progress","description":"Get current campaign progress and statistics","operationId":"get_campaign_progress_api_v1_campaign__campaign_id__progress_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignProgressResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/source-download-url":{"get":{"tags":["main"],"summary":"Get Campaign Source Download Url","description":"Get presigned download URL for campaign CSV source file\nValidates that the campaign belongs to the user's organization for security.","operationId":"get_campaign_source_download_url_api_v1_campaign__campaign_id__source_download_url_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSourceDownloadResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaign/{campaign_id}/report":{"get":{"tags":["main"],"summary":"Download Campaign Report","description":"Download a CSV report of completed campaign runs.","operationId":"download_campaign_report_api_v1_campaign__campaign_id__report_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or after this datetime (ISO 8601)","title":"Start Date"},"description":"Filter runs created on or after this datetime (ISO 8601)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter runs created on or before this datetime (ISO 8601)","title":"End Date"},"description":"Filter runs created on or before this datetime (ISO 8601)"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credentials/":{"get":{"tags":["main"],"summary":"List Credentials","description":"List all webhook credentials for the user's organization.\n\nReturns:\n List of credentials (without sensitive data)","operationId":"list_credentials_api_v1_credentials__get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CredentialResponse"},"title":"Response List Credentials Api V1 Credentials Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_credentials","x-sdk-description":"List webhook credentials available to the authenticated organization."},"post":{"tags":["main"],"summary":"Create Credential","description":"Create a new webhook credential.\n\nArgs:\n request: The credential creation request\n\nReturns:\n The created credential (without sensitive data)","operationId":"create_credential_api_v1_credentials__post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCredentialRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credentials/{credential_uuid}":{"get":{"tags":["main"],"summary":"Get Credential","description":"Get a specific webhook credential by UUID.\n\nArgs:\n credential_uuid: The UUID of the credential\n\nReturns:\n The credential (without sensitive data)","operationId":"get_credential_api_v1_credentials__credential_uuid__get","parameters":[{"name":"credential_uuid","in":"path","required":true,"schema":{"type":"string","title":"Credential Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update Credential","description":"Update a webhook credential.\n\nArgs:\n credential_uuid: The UUID of the credential to update\n request: The update request\n\nReturns:\n The updated credential (without sensitive data)","operationId":"update_credential_api_v1_credentials__credential_uuid__put","parameters":[{"name":"credential_uuid","in":"path","required":true,"schema":{"type":"string","title":"Credential Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCredentialRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Delete Credential","description":"Delete (soft delete) a webhook credential.\n\nArgs:\n credential_uuid: The UUID of the credential to delete\n\nReturns:\n Success message","operationId":"delete_credential_api_v1_credentials__credential_uuid__delete","parameters":[{"name":"credential_uuid","in":"path","required":true,"schema":{"type":"string","title":"Credential Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Credential Api V1 Credentials Credential Uuid Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/":{"get":{"tags":["main"],"summary":"List Tools","description":"List all tools for the user's organization.\n\nArgs:\n status: Optional filter by status (active, archived, draft)\n category: Optional filter by category (http_api, native, integration)\n\nReturns:\n List of tools","operationId":"list_tools_api_v1_tools__get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ToolResponse"},"title":"Response List Tools Api V1 Tools Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_tools","x-sdk-description":"List tools available to the authenticated organization."},"post":{"tags":["main"],"summary":"Create Tool","description":"Create a new tool.\n\nArgs:\n request: The tool creation request\n\nReturns:\n The created tool","operationId":"create_tool_api_v1_tools__post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateToolRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/{tool_uuid}":{"get":{"tags":["main"],"summary":"Get Tool","description":"Get a specific tool by UUID.\n\nArgs:\n tool_uuid: The UUID of the tool\n\nReturns:\n The tool","operationId":"get_tool_api_v1_tools__tool_uuid__get","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update Tool","description":"Update a tool.\n\nArgs:\n tool_uuid: The UUID of the tool to update\n request: The update request\n\nReturns:\n The updated tool","operationId":"update_tool_api_v1_tools__tool_uuid__put","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateToolRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Delete Tool","description":"Archive (soft delete) a tool.\n\nArgs:\n tool_uuid: The UUID of the tool to delete\n\nReturns:\n Success message","operationId":"delete_tool_api_v1_tools__tool_uuid__delete","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Tool Api V1 Tools Tool Uuid Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/{tool_uuid}/mcp/refresh":{"post":{"tags":["main"],"summary":"Refresh Mcp Tools","description":"Re-discover an MCP tool's server catalog and overwrite the cached\n``definition.config.discovered_tools``. Server down \u2192 200 with error\n(cache not overwritten on transient failure).","operationId":"refresh_mcp_tools_api_v1_tools__tool_uuid__mcp_refresh_post","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpRefreshResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/{tool_uuid}/unarchive":{"post":{"tags":["main"],"summary":"Unarchive Tool","description":"Unarchive a tool (restore from archived state).\n\nArgs:\n tool_uuid: The UUID of the tool to unarchive\n\nReturns:\n The unarchived tool","operationId":"unarchive_tool_api_v1_tools__tool_uuid__unarchive_post","parameters":[{"name":"tool_uuid","in":"path","required":true,"schema":{"type":"string","title":"Tool Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-providers/metadata":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Providers Metadata","description":"Return the list of available telephony providers and their form schemas.\n\nThe UI uses this to render the configuration form generically instead of\nhard-coding fields per provider. Adding a new provider only requires\ndeclaring its ui_metadata in providers//__init__.py.","operationId":"get_telephony_providers_metadata_api_v1_organizations_telephony_providers_metadata_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyProvidersMetadataResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-config-warnings":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Config Warnings","description":"Return aggregated warning counts for the current org's telephony configs.\n\nToday this surfaces only Telnyx configs missing ``webhook_public_key``;\nadditional warning types should be added as new fields on the response.","operationId":"get_telephony_config_warnings_api_v1_organizations_telephony_config_warnings_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigWarningsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs":{"get":{"tags":["main","organizations"],"summary":"List Telephony Configurations","description":"List the org's telephony configurations with phone-number counts.","operationId":"list_telephony_configurations_api_v1_organizations_telephony_configs_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationListResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Create Telephony Configuration","description":"Create a new telephony configuration for the org.","operationId":"create_telephony_configuration_api_v1_organizations_telephony_configs_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Configuration By Id","operationId":"get_telephony_configuration_by_id_api_v1_organizations_telephony_configs__config_id__get","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Update Telephony Configuration","operationId":"update_telephony_configuration_api_v1_organizations_telephony_configs__config_id__put","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","organizations"],"summary":"Delete Telephony Configuration","operationId":"delete_telephony_configuration_api_v1_organizations_telephony_configs__config_id__delete","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/set-default-outbound":{"post":{"tags":["main","organizations"],"summary":"Set Default Outbound","operationId":"set_default_outbound_api_v1_organizations_telephony_configs__config_id__set_default_outbound_post","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationDetail"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/phone-numbers":{"get":{"tags":["main","organizations"],"summary":"List Phone Numbers","operationId":"list_phone_numbers_api_v1_organizations_telephony_configs__config_id__phone_numbers_get","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberListResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Create Phone Number","operationId":"create_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers_post","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}":{"get":{"tags":["main","organizations"],"summary":"Get Phone Number","operationId":"get_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__get","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Update Phone Number","operationId":"update_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__put","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","organizations"],"summary":"Delete Phone Number","operationId":"delete_phone_number_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__delete","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}/set-default-caller":{"post":{"tags":["main","organizations"],"summary":"Set Default Caller Id","operationId":"set_default_caller_id_api_v1_organizations_telephony_configs__config_id__phone_numbers__phone_number_id__set_default_caller_post","parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"integer","title":"Config Id"}},{"name":"phone_number_id","in":"path","required":true,"schema":{"type":"integer","title":"Phone Number Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/telephony-config":{"get":{"tags":["main","organizations"],"summary":"Get Telephony Configuration","description":"Legacy: returns the org's default config in the original per-provider\nresponse shape so the existing single-form UI keeps working. Prefer the\nmulti-config endpoints (``/telephony-configs``) for new clients.","operationId":"get_telephony_configuration_api_v1_organizations_telephony_config_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelephonyConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Save Telephony Configuration","description":"Legacy: upserts the org's default config (and its phone numbers) in the\noriginal payload shape so existing UI clients keep working. Prefer the\nmulti-config + phone-number endpoints for new clients.","operationId":"save_telephony_configuration_api_v1_organizations_telephony_config_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ARIConfigurationRequest"},{"$ref":"#/components/schemas/CloudonixConfigurationRequest"},{"$ref":"#/components/schemas/PlivoConfigurationRequest"},{"$ref":"#/components/schemas/TelnyxConfigurationRequest"},{"$ref":"#/components/schemas/TwilioConfigurationRequest"},{"$ref":"#/components/schemas/VobizConfigurationRequest"},{"$ref":"#/components/schemas/VonageConfigurationRequest"}],"discriminator":{"propertyName":"provider","mapping":{"ari":"#/components/schemas/ARIConfigurationRequest","cloudonix":"#/components/schemas/CloudonixConfigurationRequest","plivo":"#/components/schemas/PlivoConfigurationRequest","telnyx":"#/components/schemas/TelnyxConfigurationRequest","twilio":"#/components/schemas/TwilioConfigurationRequest","vobiz":"#/components/schemas/VobizConfigurationRequest","vonage":"#/components/schemas/VonageConfigurationRequest"}},"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/langfuse-credentials":{"get":{"tags":["main","organizations"],"summary":"Get Langfuse Credentials","description":"Get Langfuse credentials for the user's organization with masked sensitive fields.","operationId":"get_langfuse_credentials_api_v1_organizations_langfuse_credentials_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LangfuseCredentialsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main","organizations"],"summary":"Save Langfuse Credentials","description":"Save Langfuse credentials for the user's organization.","operationId":"save_langfuse_credentials_api_v1_organizations_langfuse_credentials_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LangfuseCredentialsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","organizations"],"summary":"Delete Langfuse Credentials","description":"Delete Langfuse credentials for the user's organization.","operationId":"delete_langfuse_credentials_api_v1_organizations_langfuse_credentials_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/campaign-defaults":{"get":{"tags":["main","organizations"],"summary":"Get Campaign Defaults","description":"Get campaign limits for the user's organization.\n\nReturns the organization's concurrent call limit and default retry configuration.","operationId":"get_campaign_defaults_api_v1_organizations_campaign_defaults_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDefaultsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/s3/signed-url":{"get":{"tags":["main","s3"],"summary":"Generate a signed S3 URL","description":"Return a short-lived signed URL for a file stored on S3 / MinIO.\n\nAccess Control:\n* Keys that embed an organization ID (``{prefix}/{org_id}/...``) are\n authorized by matching the org_id against the requesting user's\n organization.\n* Legacy keys (``recordings/{run_id}.wav``, ``transcripts/{run_id}.txt``)\n are authorized via the workflow run they belong to.\n* Superusers can request any key.","operationId":"get_signed_url_api_v1_s3_signed_url_get","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","description":"S3 object key","title":"Key"},"description":"S3 object key"},{"name":"expires_in","in":"query","required":false,"schema":{"type":"integer","default":3600,"title":"Expires In"}},{"name":"inline","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Inline"}},{"name":"storage_backend","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Storage backend to use (e.g. 'minio', 's3'). When omitted the backend is inferred from the resource.","title":"Storage Backend"},"description":"Storage backend to use (e.g. 'minio', 's3'). When omitted the backend is inferred from the resource."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/S3SignedUrlResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/s3/file-metadata":{"get":{"tags":["main","s3"],"summary":"Get file metadata for debugging","description":"Get file metadata including creation timestamp for debugging.\n\nAccess Control:\n* Superusers can request any key.\n* Regular users can only request resources belonging to **their** workflow runs.","operationId":"get_file_metadata_api_v1_s3_file_metadata_get","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","description":"S3 object key","title":"Key"},"description":"S3 object key"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileMetadataResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/s3/presigned-upload-url":{"post":{"tags":["main","s3"],"summary":"Generate a presigned URL for direct CSV upload","description":"Generate a presigned PUT URL for direct CSV file upload to S3/MinIO.\n\nThis endpoint enables browser-to-storage uploads without passing through the backend\n\nAccess Control:\n* All authenticated users can upload CSV files scoped to their organization.\n* Files are stored with organization-scoped keys for multi-tenancy.\n\nReturns:\n* upload_url: Presigned URL (valid for 15 minutes) for PUT request\n* file_key: Unique storage key to use as source_id in campaign creation\n* expires_in: URL expiration time in seconds","operationId":"get_presigned_upload_url_api_v1_s3_presigned_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignedUploadUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignedUploadUrlResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/service-keys":{"get":{"tags":["main"],"summary":"Get Service Keys","description":"Get all service keys for the user's organization.","operationId":"get_service_keys_api_v1_user_service_keys_get","parameters":[{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Archived"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServiceKeyResponse"},"title":"Response Get Service Keys Api V1 User Service Keys Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main"],"summary":"Create Service Key","description":"Create a new service key for the user's organization.","operationId":"create_service_key_api_v1_user_service_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceKeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceKeyResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/service-keys/{service_key_id}":{"delete":{"tags":["main"],"summary":"Archive Service Key","description":"Archive a service key.","operationId":"archive_service_key_api_v1_user_service_keys__service_key_id__delete","parameters":[{"name":"service_key_id","in":"path","required":true,"schema":{"type":"string","title":"Service Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/user/service-keys/{service_key_id}/reactivate":{"put":{"tags":["main"],"summary":"Reactivate Service Key","description":"Reactivate an archived service key.\n\nNote: This endpoint is provided for API compatibility but service key\nreactivation is not supported by MPS. Once archived, a service key\ncannot be reactivated and a new key must be created instead.","operationId":"reactivate_service_key_api_v1_user_service_keys__service_key_id__reactivate_put","parameters":[{"name":"service_key_id","in":"path","required":true,"schema":{"type":"string","title":"Service Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/current-period":{"get":{"tags":["main"],"summary":"Get Current Period Usage","description":"Get current billing period usage for the user's organization.","operationId":"get_current_period_usage_api_v1_organizations_usage_current_period_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurrentUsageResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/mps-credits":{"get":{"tags":["main"],"summary":"Get Mps Credits","description":"Get aggregated usage and quota from MPS.\n\nOSS users: queries by provider_id (created_by).\nHosted users: queries by organization_id.","operationId":"get_mps_credits_api_v1_organizations_usage_mps_credits_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MPSCreditsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/runs":{"get":{"tags":["main"],"summary":"Get Usage History","description":"Get paginated workflow runs with usage for the organization.","operationId":"get_usage_history_api_v1_organizations_usage_runs_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`.","examples":["2026-04-01T00:00:00Z"],"title":"Start Date"},"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`.","examples":["2026-05-01T00:00:00Z"],"title":"End Date"},"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n","examples":["[{\"attribute\":\"callerNumber\",\"type\":\"text\",\"value\":{\"value\":\"415555\"}}]","[{\"attribute\":\"campaignId\",\"type\":\"number\",\"value\":{\"value\":7}},{\"attribute\":\"duration\",\"type\":\"numberRange\",\"value\":{\"min\":60,\"max\":300}}]","[{\"attribute\":\"dispositionCode\",\"type\":\"multiSelect\",\"value\":{\"codes\":[\"XFER\",\"DNC\"]}}]"],"title":"Filters"},"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageHistoryResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/runs/report":{"get":{"tags":["main"],"summary":"Download Usage Runs Report","description":"Download a CSV of runs matching the same filters as `/usage/runs`.","operationId":"download_usage_runs_report_api_v1_organizations_usage_runs_report_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`.","title":"Start Date"},"description":"ISO 8601 date-time string (UTC). Lower bound (inclusive) on `created_at`."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`.","title":"End Date"},"description":"ISO 8601 date-time string (UTC). Upper bound (inclusive) on `created_at`."},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n","title":"Filters"},"description":"JSON-encoded array of filter objects. Each object has the shape:\n\n```json\n{ \"attribute\": \"\", \"type\": \"\", \"value\": }\n```\n\nSupported `attribute` / `type` / `value` combinations:\n\n| attribute | type | value shape | matches |\n|-----------------|---------------|----------------------------------------------|------------------------------------------------------|\n| `runId` | `number` | `{ \"value\": 12345 }` | exact run id |\n| `workflowId` | `number` | `{ \"value\": 42 }` | exact agent (workflow) id |\n| `campaignId` | `number` | `{ \"value\": 7 }` | exact campaign id |\n| `callerNumber` | `text` | `{ \"value\": \"415555\" }` | substring match on `initial_context.caller_number` |\n| `calledNumber` | `text` | `{ \"value\": \"9911848\" }` | substring match on `initial_context.called_number` |\n| `dispositionCode` | `multiSelect` | `{ \"codes\": [\"XFER\", \"DNC\"] }` | any of the codes in `gathered_context.mapped_call_disposition` |\n| `duration` | `numberRange` | `{ \"min\": 60, \"max\": 300 }` | call duration (seconds), inclusive bounds |\n\nUnknown attributes and unsupported `type` values are silently ignored.\n\nDate filtering on this endpoint is done via the dedicated `start_date` / `end_date` query params, not via a `dateRange` filter object.\n"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/daily-breakdown":{"get":{"tags":["main"],"summary":"Get Daily Usage Breakdown","description":"Get daily usage breakdown for the last N days. Only available for organizations with pricing.","operationId":"get_daily_usage_breakdown_api_v1_organizations_usage_daily_breakdown_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"Number of days to include","default":7,"title":"Days"},"description":"Number of days to include"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailyUsageBreakdownResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/reports/daily":{"get":{"tags":["main"],"summary":"Get Daily Report","description":"Get daily report for the specified date and timezone.\nIf workflow_id is provided, filters results to that specific workflow.\nIf workflow_id is None, includes all workflows for the organization.","operationId":"get_daily_report_api_v1_organizations_reports_daily_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"Date in YYYY-MM-DD format","title":"Date"},"description":"Date in YYYY-MM-DD format"},{"name":"timezone","in":"query","required":true,"schema":{"type":"string","description":"IANA timezone (e.g., 'America/New_York')","title":"Timezone"},"description":"IANA timezone (e.g., 'America/New_York')"},{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Optional workflow ID to filter by","title":"Workflow Id"},"description":"Optional workflow ID to filter by"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DailyReportResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/reports/workflows":{"get":{"tags":["main"],"summary":"Get Workflow Options","description":"Get all workflows for the user's organization.\nUsed to populate the workflow selector dropdown in the reports page.","operationId":"get_workflow_options_api_v1_organizations_reports_workflows_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowOption"},"title":"Response Get Workflow Options Api V1 Organizations Reports Workflows Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/reports/daily/runs":{"get":{"tags":["main"],"summary":"Get Daily Runs Detail","description":"Get detailed workflow runs for the specified date.\nUsed for CSV export functionality.","operationId":"get_daily_runs_detail_api_v1_organizations_reports_daily_runs_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"Date in YYYY-MM-DD format","title":"Date"},"description":"Date in YYYY-MM-DD format"},{"name":"timezone","in":"query","required":true,"schema":{"type":"string","description":"IANA timezone (e.g., 'America/New_York')","title":"Timezone"},"description":"IANA timezone (e.g., 'America/New_York')"},{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Optional workflow ID to filter by","title":"Workflow Id"},"description":"Optional workflow ID to filter by"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRunDetail"},"title":"Response Get Daily Runs Detail Api V1 Organizations Reports Daily Runs Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/turn/credentials":{"get":{"tags":["main","turn"],"summary":"Get Turn Credentials","description":"Get time-limited TURN credentials for WebRTC connections.\n\nThis endpoint generates ephemeral TURN credentials that are:\n- Valid for the configured TTL (default: 24 hours)\n- Cryptographically bound to the user via HMAC\n- Compatible with coturn's use-auth-secret mode\n\nReturns:\n TurnCredentialsResponse with username, password, ttl, and TURN URIs","operationId":"get_turn_credentials_api_v1_turn_credentials_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnCredentialsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/embed/init":{"post":{"tags":["main"],"summary":"Initialize Embed Session","description":"Initialize an embed session with token validation and domain checking.\n\nThis endpoint:\n1. Validates the embed token\n2. Checks domain whitelist\n3. Creates a workflow run\n4. Generates a temporary session token\n5. Returns configuration for the widget","operationId":"initialize_embed_session_api_v1_public_embed_init_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitEmbedRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitEmbedResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"options":{"tags":["main"],"summary":"Options Init","description":"Handle CORS preflight for init endpoint","operationId":"options_init_api_v1_public_embed_init_options","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"}}}},"/api/v1/public/embed/config/{token}":{"get":{"tags":["main"],"summary":"Get Embed Config","description":"Get embed configuration without creating a session.\n\nThis endpoint is used to fetch widget configuration for display purposes\nwithout actually starting a call session.","operationId":"get_embed_config_api_v1_public_embed_config__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedConfigResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"options":{"tags":["main"],"summary":"Options Config","description":"Handle CORS preflight for config endpoint","operationId":"options_config_api_v1_public_embed_config__token__options","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/embed/turn-credentials/{session_token}":{"get":{"tags":["main"],"summary":"Get Public Turn Credentials","description":"Get TURN credentials for an embed session.\n\nThis endpoint allows embedded widgets to obtain TURN server credentials\nfor WebRTC connections without requiring authentication.\n\nArgs:\n session_token: The session token from embed initialization\n\nReturns:\n TurnCredentialsResponse with username, password, ttl, and TURN URIs","operationId":"get_public_turn_credentials_api_v1_public_embed_turn_credentials__session_token__get","parameters":[{"name":"session_token","in":"path","required":true,"schema":{"type":"string","title":"Session Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnCredentialsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"options":{"tags":["main"],"summary":"Options Turn Credentials","description":"Handle CORS preflight for TURN credentials endpoint","operationId":"options_turn_credentials_api_v1_public_embed_turn_credentials__session_token__options","parameters":[{"name":"session_token","in":"path","required":true,"schema":{"type":"string","title":"Session Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/agent/{uuid}":{"post":{"tags":["main"],"summary":"Initiate Call","description":"Initiate a phone call against the published agent.\n\nExecutes the workflow's currently released definition.","operationId":"initiate_call_api_v1_public_agent__uuid__post","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string","title":"Uuid"}},{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/agent/test/{uuid}":{"post":{"tags":["main"],"summary":"Initiate Call Test","description":"Initiate a phone call against the latest draft of the agent.\n\nUseful for verifying changes before publishing. Falls back to the\npublished definition when no draft exists.","operationId":"initiate_call_test_api_v1_public_agent_test__uuid__post","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string","title":"Uuid"}},{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerCallResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/public/download/workflow/{token}/{artifact_type}":{"get":{"tags":["main"],"summary":"Download Workflow Artifact","description":"Download a workflow recording or transcript via public access token.\n\nThis endpoint:\n1. Validates the public access token\n2. Looks up the corresponding workflow run\n3. Generates a signed URL for the requested artifact\n4. Redirects to the signed URL\n\nArgs:\n token: The public access token (UUID format)\n artifact_type: Type of artifact - \"recording\" or \"transcript\"\n inline: If true, sets Content-Disposition to inline for browser preview\n\nReturns:\n RedirectResponse to the signed URL (302 redirect)\n\nRaises:\n HTTPException 404: If token is invalid or artifact not found","operationId":"download_workflow_artifact_api_v1_public_download_workflow__token___artifact_type__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"artifact_type","in":"path","required":true,"schema":{"enum":["recording","transcript"],"type":"string","title":"Artifact Type"}},{"name":"inline","in":"query","required":false,"schema":{"type":"boolean","description":"Display inline in browser instead of download","default":false,"title":"Inline"},"description":"Display inline in browser instead of download"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/embed-token":{"post":{"tags":["main"],"summary":"Create Or Update Embed Token","description":"Create or update an embed token for a workflow.\nEach workflow can have only one active embed token.","operationId":"create_or_update_embed_token_api_v1_workflow__workflow_id__embed_token_post","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedTokenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedTokenResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["main"],"summary":"Get Embed Token","description":"Get the embed token for a workflow if it exists.","operationId":"get_embed_token_api_v1_workflow__workflow_id__embed_token_get","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/EmbedTokenResponse"},{"type":"null"}],"title":"Response Get Embed Token Api V1 Workflow Workflow Id Embed Token Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Deactivate Embed Token","description":"Deactivate the embed token for a workflow.","operationId":"deactivate_embed_token_api_v1_workflow__workflow_id__embed_token_delete","parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"integer","title":"Workflow Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Deactivate Embed Token Api V1 Workflow Workflow Id Embed Token Delete"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/upload-url":{"post":{"tags":["main","knowledge-base"],"summary":"Get presigned URL for document upload","description":"Generate a presigned PUT URL for uploading a document.\n\nThis endpoint:\n1. Generates a unique document UUID for organizing the S3 key\n2. Generates a presigned S3/MinIO URL for uploading the file\n3. Returns the upload URL and document metadata\n\nAfter uploading to the returned URL, call /process-document to create\nthe document record and trigger processing.\n\nAccess Control:\n* All authenticated users can upload documents scoped to their organization.","operationId":"get_upload_url_api_v1_knowledge_base_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentUploadRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentUploadResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/process-document":{"post":{"tags":["main","knowledge-base"],"summary":"Trigger document processing","description":"Trigger asynchronous processing of an uploaded document.\n\nThis endpoint should be called after successfully uploading a file to the presigned URL.\nIt will:\n1. Create a document record in the database with the specified UUID\n2. Enqueue a background task to process the document (chunking and embedding)\n\nThe document status will be updated from 'pending' -> 'processing' -> 'completed' or 'failed'.\n\nEmbedding:\nUses OpenAI text-embedding-3-small (1536-dimensional embeddings, requires API key configured in Model Configurations).\n\nAccess Control:\n* Users can only process documents in their organization.","operationId":"process_document_api_v1_knowledge_base_process_document_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessDocumentRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/documents":{"get":{"tags":["main","knowledge-base"],"summary":"List documents","description":"List all documents for the user's organization.\n\nAccess Control:\n* Users can only see documents from their organization.","operationId":"list_documents_api_v1_knowledge_base_documents_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by processing status","title":"Status"},"description":"Filter by processing status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentListResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_documents","x-sdk-description":"List knowledge base documents available to the authenticated organization."}},"/api/v1/knowledge-base/documents/{document_uuid}":{"get":{"tags":["main","knowledge-base"],"summary":"Get document details","description":"Get details of a specific document.\n\nAccess Control:\n* Users can only access documents from their organization.","operationId":"get_document_api_v1_knowledge_base_documents__document_uuid__get","parameters":[{"name":"document_uuid","in":"path","required":true,"schema":{"type":"string","title":"Document Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main","knowledge-base"],"summary":"Delete document","description":"Soft delete a document and its chunks.\n\nAccess Control:\n* Users can only delete documents from their organization.","operationId":"delete_document_api_v1_knowledge_base_documents__document_uuid__delete","parameters":[{"name":"document_uuid","in":"path","required":true,"schema":{"type":"string","title":"Document Uuid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/knowledge-base/search":{"post":{"tags":["main","knowledge-base"],"summary":"Search for similar chunks","description":"Search for document chunks similar to the query.\n\nThis endpoint uses vector similarity search to find relevant chunks.\nResults are returned without threshold filtering - apply similarity\nthresholds at the application layer after optional reranking.\n\nAccess Control:\n* Users can only search documents from their organization.","operationId":"search_chunks_api_v1_knowledge_base_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChunkSearchRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChunkSearchResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/upload-url":{"post":{"tags":["main","workflow-recordings"],"summary":"Get presigned URLs for recording uploads","description":"Generate presigned PUT URLs for uploading one or more audio recordings.","operationId":"get_upload_urls_api_v1_workflow_recordings_upload_url_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingUploadRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingUploadResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/":{"post":{"tags":["main","workflow-recordings"],"summary":"Create recording records after upload","description":"Create one or more recording records after audio files have been uploaded.","operationId":"create_recordings_api_v1_workflow_recordings__post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingCreateRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordingCreateResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["main","workflow-recordings"],"summary":"List recordings","description":"List recordings for the organization, optionally filtered.","operationId":"list_recordings_api_v1_workflow_recordings__get","parameters":[{"name":"workflow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by workflow ID","title":"Workflow Id"},"description":"Filter by workflow ID"},{"name":"tts_provider","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TTS provider","title":"Tts Provider"},"description":"Filter by TTS provider"},{"name":"tts_model","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TTS model","title":"Tts Model"},"description":"Filter by TTS model"},{"name":"tts_voice_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by TTS voice ID","title":"Tts Voice Id"},"description":"Filter by TTS voice ID"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingListResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_recordings","x-sdk-description":"List workflow recordings available to the authenticated organization."}},"/api/v1/workflow-recordings/{recording_id}":{"delete":{"tags":["main","workflow-recordings"],"summary":"Delete a recording","description":"Soft delete a recording.","operationId":"delete_recording_api_v1_workflow_recordings__recording_id__delete","parameters":[{"name":"recording_id","in":"path","required":true,"schema":{"type":"string","title":"Recording Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/{id}":{"patch":{"tags":["main","workflow-recordings"],"summary":"Update a recording's Recording ID","description":"Update the recording_id (descriptive name) of a recording.","operationId":"update_recording_api_v1_workflow_recordings__id__patch","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","title":"Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingUpdateRequestSchema"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingResponseSchema"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow-recordings/transcribe":{"post":{"tags":["main","workflow-recordings"],"summary":"Transcribe an audio file","description":"Transcribe an uploaded audio file using MPS STT.","operationId":"transcribe_audio_api_v1_workflow_recordings_transcribe_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_transcribe_audio_api_v1_workflow_recordings_transcribe_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/signup":{"post":{"tags":["main","auth"],"summary":"Signup","operationId":"signup_api_v1_auth_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["main","auth"],"summary":"Login","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["main","auth"],"summary":"Get Current User","operationId":"get_current_user_api_v1_auth_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/node-types":{"get":{"tags":["main"],"summary":"List Node Types","description":"List every registered NodeSpec.\n\nSDK clients should pin to `spec_version` and warn if the server reports\na higher version than what they were generated against.","operationId":"list_node_types_api_v1_node_types_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeTypesResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"list_node_types","x-sdk-description":"List every registered node type with its spec. Pinned to spec_version."}},"/api/v1/node-types/{name}":{"get":{"tags":["main"],"summary":"Get Node Type","operationId":"get_node_type_api_v1_node_types__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeSpec"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-sdk-method":"get_node_type","x-sdk-description":"Fetch a single node spec by name."}},"/api/v1/health":{"get":{"tags":["main"],"summary":"Health","operationId":"health_api_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}},"404":{"description":"Not found"}}}}},"components":{"schemas":{"APIKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"archived_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Archived At"}},"type":"object","required":["id","name","key_prefix","is_active","created_at"],"title":"APIKeyResponse"},"APIKeyStatus":{"properties":{"model":{"type":"string","title":"Model"},"message":{"type":"string","title":"Message"}},"type":"object","required":["model","message"],"title":"APIKeyStatus"},"APIKeyStatusResponse":{"properties":{"status":{"items":{"$ref":"#/components/schemas/APIKeyStatus"},"type":"array","title":"Status"}},"type":"object","required":["status"],"title":"APIKeyStatusResponse"},"ARIConfigurationRequest":{"properties":{"provider":{"type":"string","const":"ari","title":"Provider","default":"ari"},"ari_endpoint":{"type":"string","title":"Ari Endpoint","description":"ARI base URL (e.g., http://asterisk.example.com:8088)"},"app_name":{"type":"string","title":"App Name","description":"Stasis application name registered in Asterisk"},"app_password":{"type":"string","title":"App Password","description":"ARI user password"},"ws_client_name":{"type":"string","title":"Ws Client Name","description":"websocket_client.conf connection name for externalMedia (e.g., dograh_staging)","default":""},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of SIP extensions/numbers for outbound calls (optional)"}},"type":"object","required":["ari_endpoint","app_name","app_password"],"title":"ARIConfigurationRequest","description":"Request schema for Asterisk ARI configuration."},"ARIConfigurationResponse":{"properties":{"provider":{"type":"string","const":"ari","title":"Provider","default":"ari"},"ari_endpoint":{"type":"string","title":"Ari Endpoint"},"app_name":{"type":"string","title":"App Name"},"app_password":{"type":"string","title":"App Password"},"ws_client_name":{"type":"string","title":"Ws Client Name","default":""},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["ari_endpoint","app_name","app_password","from_numbers"],"title":"ARIConfigurationResponse","description":"Response schema for ARI configuration with masked sensitive fields."},"AmbientNoiseUploadRequest":{"properties":{"workflow_id":{"type":"integer","title":"Workflow Id"},"filename":{"type":"string","title":"Filename"},"mime_type":{"type":"string","title":"Mime Type","default":"audio/wav"},"file_size":{"type":"integer","maximum":10485760.0,"exclusiveMinimum":0.0,"title":"File Size","description":"Max 10MB"}},"type":"object","required":["workflow_id","filename","file_size"],"title":"AmbientNoiseUploadRequest"},"AmbientNoiseUploadResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"storage_key":{"type":"string","title":"Storage Key"},"storage_backend":{"type":"string","title":"Storage Backend"}},"type":"object","required":["upload_url","storage_key","storage_backend"],"title":"AmbientNoiseUploadResponse"},"AuthResponse":{"properties":{"token":{"type":"string","title":"Token"},"user":{"$ref":"#/components/schemas/UserResponse"}},"type":"object","required":["token","user"],"title":"AuthResponse"},"AuthUserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"is_superuser":{"type":"boolean","title":"Is Superuser"}},"type":"object","required":["id","is_superuser"],"title":"AuthUserResponse"},"BatchRecordingCreateRequestSchema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/RecordingCreateRequestSchema"},"type":"array","maxItems":20,"minItems":1,"title":"Recordings","description":"List of recordings to create"}},"type":"object","required":["recordings"],"title":"BatchRecordingCreateRequestSchema","description":"Request schema for creating one or more recording records after upload."},"BatchRecordingCreateResponseSchema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/RecordingResponseSchema"},"type":"array","title":"Recordings","description":"Created recording records"}},"type":"object","required":["recordings"],"title":"BatchRecordingCreateResponseSchema","description":"Response schema for recording creation."},"BatchRecordingUploadRequestSchema":{"properties":{"files":{"items":{"$ref":"#/components/schemas/FileDescriptor"},"type":"array","maxItems":20,"minItems":1,"title":"Files","description":"List of files to upload"}},"type":"object","required":["files"],"title":"BatchRecordingUploadRequestSchema","description":"Request schema for getting presigned upload URLs for one or more files."},"BatchRecordingUploadResponseSchema":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RecordingUploadResponseSchema"},"type":"array","title":"Items","description":"Upload URLs for each file"}},"type":"object","required":["items"],"title":"BatchRecordingUploadResponseSchema","description":"Response schema with presigned upload URLs."},"Body_transcribe_audio_api_v1_workflow_recordings_transcribe_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"language":{"type":"string","title":"Language","default":"en"}},"type":"object","required":["file"],"title":"Body_transcribe_audio_api_v1_workflow_recordings_transcribe_post"},"CalculatorToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"calculator","title":"Type","description":"Tool type"}},"type":"object","required":["type"],"title":"CalculatorToolDefinition","description":"Tool definition for Calculator tools (no configuration needed)."},"CallDispositionCodes":{"properties":{"disposition_codes":{"items":{"type":"string"},"type":"array","title":"Disposition Codes","default":[]}},"type":"object","title":"CallDispositionCodes"},"CallType":{"type":"string","enum":["inbound","outbound"],"title":"CallType"},"CampaignDefaultsResponse":{"properties":{"concurrent_call_limit":{"type":"integer","title":"Concurrent Call Limit"},"from_numbers_count":{"type":"integer","title":"From Numbers Count"},"default_retry_config":{"$ref":"#/components/schemas/RetryConfigResponse"},"last_campaign_settings":{"anyOf":[{"$ref":"#/components/schemas/LastCampaignSettingsResponse"},{"type":"null"}]}},"type":"object","required":["concurrent_call_limit","from_numbers_count","default_retry_config"],"title":"CampaignDefaultsResponse"},"CampaignLogEntryResponse":{"properties":{"ts":{"type":"string","title":"Ts"},"level":{"type":"string","title":"Level"},"event":{"type":"string","title":"Event"},"message":{"type":"string","title":"Message"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"}},"type":"object","required":["ts","level","event","message"],"title":"CampaignLogEntryResponse","description":"A single timestamped entry from the campaign's append-only log.\n\nSurfaced in the UI so operators can see why a campaign moved to\npaused / failed without digging through server logs."},"CampaignProgressResponse":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"state":{"type":"string","title":"State"},"total_rows":{"type":"integer","title":"Total Rows"},"processed_rows":{"type":"integer","title":"Processed Rows"},"failed_calls":{"type":"integer","title":"Failed Calls"},"progress_percentage":{"type":"number","title":"Progress Percentage"},"source_sync":{"additionalProperties":true,"type":"object","title":"Source Sync"},"rate_limit":{"type":"integer","title":"Rate Limit"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["campaign_id","state","total_rows","processed_rows","failed_calls","progress_percentage","source_sync","rate_limit","started_at","completed_at"],"title":"CampaignProgressResponse"},"CampaignResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_name":{"type":"string","title":"Workflow Name"},"state":{"type":"string","title":"State"},"source_type":{"type":"string","title":"Source Type"},"source_id":{"type":"string","title":"Source Id"},"total_rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Rows"},"processed_rows":{"type":"integer","title":"Processed Rows"},"failed_rows":{"type":"integer","title":"Failed Rows"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"retry_config":{"$ref":"#/components/schemas/RetryConfigResponse"},"max_concurrency":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigResponse"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigResponse"},{"type":"null"}]},"executed_count":{"type":"integer","title":"Executed Count","default":0},"total_queued_count":{"type":"integer","title":"Total Queued Count","default":0},"parent_campaign_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent Campaign Id"},"redialed_campaign_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Redialed Campaign Id"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"},"telephony_configuration_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Telephony Configuration Name"},"logs":{"items":{"$ref":"#/components/schemas/CampaignLogEntryResponse"},"type":"array","title":"Logs"}},"type":"object","required":["id","name","workflow_id","workflow_name","state","source_type","source_id","total_rows","processed_rows","failed_rows","created_at","started_at","completed_at","retry_config"],"title":"CampaignResponse"},"CampaignRunsResponse":{"properties":{"runs":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Runs"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["runs","total_count","page","limit","total_pages"],"title":"CampaignRunsResponse","description":"Paginated response for campaign workflow runs"},"CampaignSourceDownloadResponse":{"properties":{"download_url":{"type":"string","title":"Download Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["download_url","expires_in"],"title":"CampaignSourceDownloadResponse"},"CampaignsResponse":{"properties":{"campaigns":{"items":{"$ref":"#/components/schemas/CampaignResponse"},"type":"array","title":"Campaigns"}},"type":"object","required":["campaigns"],"title":"CampaignsResponse"},"ChunkResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"document_id":{"type":"integer","title":"Document Id"},"chunk_text":{"type":"string","title":"Chunk Text"},"contextualized_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contextualized Text"},"chunk_index":{"type":"integer","title":"Chunk Index"},"chunk_metadata":{"additionalProperties":true,"type":"object","title":"Chunk Metadata"},"filename":{"type":"string","title":"Filename"},"document_uuid":{"type":"string","title":"Document Uuid"},"similarity":{"type":"number","title":"Similarity"}},"type":"object","required":["id","document_id","chunk_text","contextualized_text","chunk_index","chunk_metadata","filename","document_uuid","similarity"],"title":"ChunkResponseSchema","description":"Response schema for a document chunk."},"ChunkSearchRequestSchema":{"properties":{"query":{"type":"string","title":"Query","description":"Search query text"},"limit":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Limit","description":"Maximum number of results","default":5},"document_uuids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Document Uuids","description":"Filter by specific document UUIDs"},"min_similarity":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Min Similarity","description":"Minimum similarity threshold"}},"type":"object","required":["query"],"title":"ChunkSearchRequestSchema","description":"Request schema for searching similar chunks."},"ChunkSearchResponseSchema":{"properties":{"chunks":{"items":{"$ref":"#/components/schemas/ChunkResponseSchema"},"type":"array","title":"Chunks"},"query":{"type":"string","title":"Query"},"total_results":{"type":"integer","title":"Total Results"}},"type":"object","required":["chunks","query","total_results"],"title":"ChunkSearchResponseSchema","description":"Response schema for chunk search results."},"CircuitBreakerConfigRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"failure_threshold":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Failure Threshold","default":0.5},"window_seconds":{"type":"integer","maximum":600.0,"minimum":30.0,"title":"Window Seconds","default":120},"min_calls_in_window":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Min Calls In Window","default":5}},"type":"object","title":"CircuitBreakerConfigRequest"},"CircuitBreakerConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":false},"failure_threshold":{"type":"number","title":"Failure Threshold","default":0.5},"window_seconds":{"type":"integer","title":"Window Seconds","default":120},"min_calls_in_window":{"type":"integer","title":"Min Calls In Window","default":5}},"type":"object","title":"CircuitBreakerConfigResponse"},"CloudonixConfigurationRequest":{"properties":{"provider":{"type":"string","const":"cloudonix","title":"Provider","default":"cloudonix"},"bearer_token":{"type":"string","title":"Bearer Token","description":"Cloudonix API Bearer Token"},"domain_id":{"type":"string","title":"Domain Id","description":"Cloudonix Domain ID"},"application_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Name","description":"Cloudonix Voice Application name. The application's url is updated when inbound workflows are attached to numbers on this domain. If omitted, an application is auto-created on save and its name is stored on the configuration."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Cloudonix phone numbers (optional)"}},"type":"object","required":["bearer_token","domain_id"],"title":"CloudonixConfigurationRequest","description":"Request schema for Cloudonix configuration."},"CloudonixConfigurationResponse":{"properties":{"provider":{"type":"string","const":"cloudonix","title":"Provider","default":"cloudonix"},"bearer_token":{"type":"string","title":"Bearer Token"},"domain_id":{"type":"string","title":"Domain Id"},"application_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Name"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["bearer_token","domain_id","from_numbers"],"title":"CloudonixConfigurationResponse","description":"Response schema for Cloudonix configuration with masked sensitive fields."},"CreateAPIKeyRequest":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"CreateAPIKeyRequest"},"CreateAPIKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"api_key":{"type":"string","title":"Api Key"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","key_prefix","api_key","created_at"],"title":"CreateAPIKeyResponse"},"CreateCampaignRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"workflow_id":{"type":"integer","title":"Workflow Id"},"source_type":{"type":"string","pattern":"^csv$","title":"Source Type"},"source_id":{"type":"string","title":"Source Id"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"},"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigRequest"},{"type":"null"}]},"max_concurrency":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":1.0},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigRequest"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigRequest"},{"type":"null"}]}},"type":"object","required":["name","workflow_id","source_type","source_id"],"title":"CreateCampaignRequest"},"CreateCredentialRequest":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"credential_type":{"$ref":"#/components/schemas/WebhookCredentialType"},"credential_data":{"additionalProperties":true,"type":"object","title":"Credential Data"}},"type":"object","required":["name","credential_type","credential_data"],"title":"CreateCredentialRequest","description":"Request schema for creating a webhook credential."},"CreateServiceKeyRequest":{"properties":{"name":{"type":"string","title":"Name"},"expires_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In Days","default":90}},"type":"object","required":["name"],"title":"CreateServiceKeyRequest"},"CreateServiceKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"service_key":{"type":"string","title":"Service Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","name","service_key","key_prefix"],"title":"CreateServiceKeyResponse"},"CreateToolRequest":{"properties":{"name":{"type":"string","maxLength":255,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"type":"string","title":"Category","default":"http_api"},"icon":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Icon","default":"globe"},"icon_color":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Icon Color","default":"#3B82F6"},"definition":{"oneOf":[{"$ref":"#/components/schemas/HttpApiToolDefinition"},{"$ref":"#/components/schemas/EndCallToolDefinition"},{"$ref":"#/components/schemas/TransferCallToolDefinition"},{"$ref":"#/components/schemas/CalculatorToolDefinition"},{"$ref":"#/components/schemas/McpToolDefinition"}],"title":"Definition","discriminator":{"propertyName":"type","mapping":{"calculator":"#/components/schemas/CalculatorToolDefinition","end_call":"#/components/schemas/EndCallToolDefinition","http_api":"#/components/schemas/HttpApiToolDefinition","mcp":"#/components/schemas/McpToolDefinition","transfer_call":"#/components/schemas/TransferCallToolDefinition"}}}},"type":"object","required":["name","definition"],"title":"CreateToolRequest","description":"Request schema for creating a tool."},"CreateWorkflowRequest":{"properties":{"name":{"type":"string","title":"Name"},"workflow_definition":{"additionalProperties":true,"type":"object","title":"Workflow Definition"}},"type":"object","required":["name","workflow_definition"],"title":"CreateWorkflowRequest"},"CreateWorkflowRunRequest":{"properties":{"mode":{"type":"string","title":"Mode"},"name":{"type":"string","title":"Name"}},"type":"object","required":["mode","name"],"title":"CreateWorkflowRunRequest"},"CreateWorkflowRunResponse":{"properties":{"id":{"type":"integer","title":"Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"name":{"type":"string","title":"Name"},"mode":{"type":"string","title":"Mode"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"definition_id":{"type":"integer","title":"Definition Id"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"}},"type":"object","required":["id","workflow_id","name","mode","created_at","definition_id"],"title":"CreateWorkflowRunResponse"},"CreateWorkflowTemplateRequest":{"properties":{"call_type":{"type":"string","enum":["inbound","outbound"],"title":"Call Type"},"use_case":{"type":"string","title":"Use Case"},"activity_description":{"type":"string","title":"Activity Description"}},"type":"object","required":["call_type","use_case","activity_description"],"title":"CreateWorkflowTemplateRequest"},"CreatedByResponse":{"properties":{"id":{"type":"integer","title":"Id"},"provider_id":{"type":"string","title":"Provider Id"}},"type":"object","required":["id","provider_id"],"title":"CreatedByResponse","description":"Response schema for the user who created a tool."},"CredentialResponse":{"properties":{"uuid":{"type":"string","title":"Uuid"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"credential_type":{"type":"string","title":"Credential Type"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["uuid","name","description","credential_type","created_at","updated_at"],"title":"CredentialResponse","description":"Response schema for a webhook credential (never includes sensitive data)."},"CurrentUsageResponse":{"properties":{"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"used_dograh_tokens":{"type":"number","title":"Used Dograh Tokens"},"quota_dograh_tokens":{"type":"integer","title":"Quota Dograh Tokens"},"percentage_used":{"type":"number","title":"Percentage Used"},"next_refresh_date":{"type":"string","title":"Next Refresh Date"},"quota_enabled":{"type":"boolean","title":"Quota Enabled"},"total_duration_seconds":{"type":"integer","title":"Total Duration Seconds"},"used_amount_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Used Amount Usd"},"quota_amount_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quota Amount Usd"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"price_per_second_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price Per Second Usd"}},"type":"object","required":["period_start","period_end","used_dograh_tokens","quota_dograh_tokens","percentage_used","next_refresh_date","quota_enabled","total_duration_seconds"],"title":"CurrentUsageResponse"},"DailyReportResponse":{"properties":{"date":{"type":"string","title":"Date"},"timezone":{"type":"string","title":"Timezone"},"workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Id"},"metrics":{"additionalProperties":{"type":"integer"},"type":"object","title":"Metrics"},"disposition_distribution":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Disposition Distribution"},"call_duration_distribution":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Call Duration Distribution"}},"type":"object","required":["date","timezone","workflow_id","metrics","disposition_distribution","call_duration_distribution"],"title":"DailyReportResponse"},"DailyUsageBreakdownResponse":{"properties":{"breakdown":{"items":{"$ref":"#/components/schemas/DailyUsageItem"},"type":"array","title":"Breakdown"},"total_minutes":{"type":"number","title":"Total Minutes"},"total_cost_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Cost Usd"},"total_dograh_tokens":{"type":"number","title":"Total Dograh Tokens"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"}},"type":"object","required":["breakdown","total_minutes","total_dograh_tokens"],"title":"DailyUsageBreakdownResponse"},"DailyUsageItem":{"properties":{"date":{"type":"string","title":"Date"},"minutes":{"type":"number","title":"Minutes"},"cost_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Usd"},"dograh_tokens":{"type":"number","title":"Dograh Tokens"},"call_count":{"type":"integer","title":"Call Count"}},"type":"object","required":["date","minutes","dograh_tokens","call_count"],"title":"DailyUsageItem"},"DefaultConfigurationsResponse":{"properties":{"llm":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Llm"},"tts":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Tts"},"stt":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Stt"},"embeddings":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Embeddings"},"realtime":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Realtime"},"default_providers":{"additionalProperties":{"type":"string"},"type":"object","title":"Default Providers"}},"type":"object","required":["llm","tts","stt","embeddings","realtime","default_providers"],"title":"DefaultConfigurationsResponse"},"DisplayOptions":{"properties":{"show":{"anyOf":[{"additionalProperties":{"items":{},"type":"array"},"type":"object"},{"type":"null"}],"title":"Show"},"hide":{"anyOf":[{"additionalProperties":{"items":{},"type":"array"},"type":"object"},{"type":"null"}],"title":"Hide"}},"additionalProperties":false,"type":"object","title":"DisplayOptions","description":"Conditional visibility rules.\n\n`show` keys are AND-combined: this property is visible only when EVERY\nreferenced field's value matches one of the listed values.\n\n`hide` keys are OR-combined: this property is hidden when ANY referenced\nfield's value matches one of the listed values.\n\nExample:\n DisplayOptions(show={\"extraction_enabled\": [True]})\n DisplayOptions(show={\"greeting_type\": [\"audio\"]})"},"DocumentListResponseSchema":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/DocumentResponseSchema"},"type":"array","title":"Documents"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["documents","total","limit","offset"],"title":"DocumentListResponseSchema","description":"Response schema for list of documents."},"DocumentResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"document_uuid":{"type":"string","title":"Document Uuid"},"filename":{"type":"string","title":"Filename"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"file_hash":{"type":"string","title":"File Hash"},"mime_type":{"type":"string","title":"Mime Type"},"processing_status":{"type":"string","title":"Processing Status"},"processing_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Processing Error"},"total_chunks":{"type":"integer","title":"Total Chunks"},"retrieval_mode":{"type":"string","title":"Retrieval Mode","default":"chunked"},"custom_metadata":{"additionalProperties":true,"type":"object","title":"Custom Metadata"},"docling_metadata":{"additionalProperties":true,"type":"object","title":"Docling Metadata"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"organization_id":{"type":"integer","title":"Organization Id"},"created_by":{"type":"integer","title":"Created By"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","document_uuid","filename","file_size_bytes","file_hash","mime_type","processing_status","total_chunks","custom_metadata","docling_metadata","created_at","updated_at","organization_id","created_by","is_active"],"title":"DocumentResponseSchema","description":"Response schema for document metadata."},"DocumentUploadRequestSchema":{"properties":{"filename":{"type":"string","title":"Filename","description":"Name of the file to upload"},"mime_type":{"type":"string","title":"Mime Type","description":"MIME type of the file"},"custom_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Custom Metadata","description":"Optional custom metadata"}},"type":"object","required":["filename","mime_type"],"title":"DocumentUploadRequestSchema","description":"Request schema for initiating document upload."},"DocumentUploadResponseSchema":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Signed URL for uploading the file"},"document_uuid":{"type":"string","title":"Document Uuid","description":"Unique identifier for the document"},"s3_key":{"type":"string","title":"S3 Key","description":"S3 key where file should be uploaded"}},"type":"object","required":["upload_url","document_uuid","s3_key"],"title":"DocumentUploadResponseSchema","description":"Response schema containing upload URL and document metadata."},"DuplicateTemplateRequest":{"properties":{"template_id":{"type":"integer","title":"Template Id"},"workflow_name":{"type":"string","title":"Workflow Name"}},"type":"object","required":["template_id","workflow_name"],"title":"DuplicateTemplateRequest"},"EmbedConfigResponse":{"properties":{"workflow_id":{"type":"integer","title":"Workflow Id"},"settings":{"additionalProperties":true,"type":"object","title":"Settings"},"theme":{"type":"string","title":"Theme"},"position":{"type":"string","title":"Position"},"button_text":{"type":"string","title":"Button Text"},"button_color":{"type":"string","title":"Button Color"},"size":{"type":"string","title":"Size"},"auto_start":{"type":"boolean","title":"Auto Start"}},"type":"object","required":["workflow_id","settings","theme","position","button_text","button_color","size","auto_start"],"title":"EmbedConfigResponse","description":"Response model for embed configuration"},"EmbedTokenRequest":{"properties":{"allowed_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Domains"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"usage_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Usage Limit"},"expires_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In Days","default":30}},"type":"object","title":"EmbedTokenRequest"},"EmbedTokenResponse":{"properties":{"id":{"type":"integer","title":"Id"},"token":{"type":"string","title":"Token"},"allowed_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Domains"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"is_active":{"type":"boolean","title":"Is Active"},"usage_count":{"type":"integer","title":"Usage Count"},"usage_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Usage Limit"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"embed_script":{"type":"string","title":"Embed Script"}},"type":"object","required":["id","token","allowed_domains","settings","is_active","usage_count","usage_limit","expires_at","created_at","embed_script"],"title":"EmbedTokenResponse"},"EndCallConfig":{"properties":{"messageType":{"type":"string","enum":["none","custom","audio"],"title":"Messagetype","description":"Type of goodbye message","default":"none"},"customMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessage","description":"Custom message to play before ending the call"},"audioRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audiorecordingid","description":"Recording ID for audio goodbye message"},"endCallReason":{"type":"boolean","title":"Endcallreason","description":"When enabled, LLM must provide a reason for ending the call. The reason is set as call disposition and added to call tags.","default":false},"endCallReasonDescription":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endcallreasondescription","description":"Description shown to the LLM for the reason parameter. Used only when endCallReason is enabled."}},"type":"object","title":"EndCallConfig","description":"Configuration for End Call tools."},"EndCallToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"end_call","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/EndCallConfig","description":"End Call configuration"}},"type":"object","required":["type","config"],"title":"EndCallToolDefinition","description":"Tool definition for End Call tools."},"FileDescriptor":{"properties":{"filename":{"type":"string","title":"Filename","description":"Original filename of the audio file"},"mime_type":{"type":"string","title":"Mime Type","description":"MIME type of the audio file","default":"audio/wav"},"file_size":{"type":"integer","maximum":5242880.0,"exclusiveMinimum":0.0,"title":"File Size","description":"File size in bytes (max 5MB)"}},"type":"object","required":["filename","file_size"],"title":"FileDescriptor","description":"Descriptor for a single file in a batch upload request."},"FileMetadataResponse":{"properties":{"key":{"type":"string","title":"Key"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["key","metadata"],"title":"FileMetadataResponse"},"GraphConstraints":{"properties":{"min_incoming":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Incoming"},"max_incoming":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Incoming"},"min_outgoing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Outgoing"},"max_outgoing":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Outgoing"}},"additionalProperties":false,"type":"object","title":"GraphConstraints","description":"Per-node-type graph rules. WorkflowGraph enforces these at validation."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"version":{"type":"string","title":"Version"},"backend_api_endpoint":{"type":"string","title":"Backend Api Endpoint"},"deployment_mode":{"type":"string","title":"Deployment Mode"},"auth_provider":{"type":"string","title":"Auth Provider"},"turn_enabled":{"type":"boolean","title":"Turn Enabled"},"force_turn_relay":{"type":"boolean","title":"Force Turn Relay"}},"type":"object","required":["status","version","backend_api_endpoint","deployment_mode","auth_provider","turn_enabled","force_turn_relay"],"title":"HealthResponse"},"HttpApiConfig":{"properties":{"method":{"type":"string","title":"Method","description":"HTTP method (GET, POST, PUT, PATCH, DELETE)"},"url":{"type":"string","title":"Url","description":"Target URL"},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers","description":"Static headers to include"},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to ExternalCredentialModel for auth"},"parameters":{"anyOf":[{"items":{"$ref":"#/components/schemas/ToolParameter"},"type":"array"},{"type":"null"}],"title":"Parameters","description":"Parameters that the tool accepts from LLM"},"preset_parameters":{"anyOf":[{"items":{"$ref":"#/components/schemas/PresetToolParameter"},"type":"array"},{"type":"null"}],"title":"Preset Parameters","description":"Parameters injected by Dograh from fixed values or workflow context templates"},"timeout_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timeout Ms","description":"Request timeout in milliseconds","default":5000},"customMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessage","description":"Custom message to play after tool execution"},"customMessageType":{"anyOf":[{"type":"string","enum":["text","audio"]},{"type":"null"}],"title":"Custommessagetype","description":"Type of custom message: text or audio"},"customMessageRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessagerecordingid","description":"Recording ID for audio custom message"}},"type":"object","required":["method","url"],"title":"HttpApiConfig","description":"Configuration for HTTP API tools."},"HttpApiToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"http_api","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/HttpApiConfig","description":"HTTP API configuration"}},"type":"object","required":["type","config"],"title":"HttpApiToolDefinition","description":"Tool definition for HTTP API tools."},"ImpersonateRequest":{"properties":{"provider_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider User Id"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"}},"type":"object","title":"ImpersonateRequest","description":"Request payload for superadmin impersonation.\n\nEither ``provider_user_id`` **or** ``user_id`` must be supplied. If both are\nprovided, ``provider_user_id`` takes precedence."},"ImpersonateResponse":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"},"access_token":{"type":"string","title":"Access Token"}},"type":"object","required":["refresh_token","access_token"],"title":"ImpersonateResponse"},"InitEmbedRequest":{"properties":{"token":{"type":"string","title":"Token"},"context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Context Variables"}},"type":"object","required":["token"],"title":"InitEmbedRequest","description":"Request model for initializing an embed session"},"InitEmbedResponse":{"properties":{"session_token":{"type":"string","title":"Session Token"},"workflow_run_id":{"type":"integer","title":"Workflow Run Id"},"config":{"additionalProperties":true,"type":"object","title":"Config"}},"type":"object","required":["session_token","workflow_run_id","config"],"title":"InitEmbedResponse","description":"Response model for embed initialization"},"InitiateCallRequest":{"properties":{"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_run_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Run Id"},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"},"from_phone_number_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"From Phone Number Id"}},"type":"object","required":["workflow_id"],"title":"InitiateCallRequest"},"ItemKind":{"type":"string","enum":["node","edge","workflow"],"title":"ItemKind"},"LangfuseCredentialsRequest":{"properties":{"host":{"type":"string","title":"Host"},"public_key":{"type":"string","title":"Public Key"},"secret_key":{"type":"string","title":"Secret Key"}},"type":"object","required":["host","public_key","secret_key"],"title":"LangfuseCredentialsRequest"},"LangfuseCredentialsResponse":{"properties":{"host":{"type":"string","title":"Host","default":""},"public_key":{"type":"string","title":"Public Key","default":""},"secret_key":{"type":"string","title":"Secret Key","default":""},"configured":{"type":"boolean","title":"Configured","default":false}},"type":"object","title":"LangfuseCredentialsResponse"},"LastCampaignSettingsResponse":{"properties":{"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigResponse"},{"type":"null"}]},"max_concurrency":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigResponse"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigResponse"},{"type":"null"}]}},"type":"object","title":"LastCampaignSettingsResponse"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"MPSCreditsResponse":{"properties":{"total_credits_used":{"type":"number","title":"Total Credits Used"},"remaining_credits":{"type":"number","title":"Remaining Credits"},"total_quota":{"type":"number","title":"Total Quota"}},"type":"object","required":["total_credits_used","remaining_credits","total_quota"],"title":"MPSCreditsResponse"},"McpRefreshResponse":{"properties":{"tool_uuid":{"type":"string","title":"Tool Uuid"},"discovered_tools":{"items":{},"type":"array","title":"Discovered Tools"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["tool_uuid"],"title":"McpRefreshResponse","description":"Result of re-discovering an MCP server's tool catalog."},"McpToolConfig":{"properties":{"transport":{"type":"string","const":"streamable_http","title":"Transport","description":"MCP transport protocol","default":"streamable_http"},"url":{"type":"string","title":"Url","description":"MCP server URL (must be http:// or https://)"},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to ExternalCredentialModel for auth"},"tools_filter":{"items":{"type":"string"},"type":"array","title":"Tools Filter","description":"Allowlist of MCP tool names to expose (empty = all tools)"},"timeout_secs":{"type":"integer","title":"Timeout Secs","description":"Connection timeout in seconds","default":30},"sse_read_timeout_secs":{"type":"integer","title":"Sse Read Timeout Secs","description":"SSE read timeout in seconds","default":300},"discovered_tools":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Discovered Tools","description":"Server-managed cache of the MCP server's tool catalog [{name, description}]. Populated best-effort by the backend."}},"type":"object","required":["url"],"title":"McpToolConfig","description":"Configuration for an MCP tool definition."},"McpToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"mcp","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/McpToolConfig","description":"MCP server configuration"}},"type":"object","required":["type","config"],"title":"McpToolDefinition","description":"Persisted MCP tool definition."},"NodeCategory":{"type":"string","enum":["call_node","global_node","trigger","integration"],"title":"NodeCategory","description":"Drives grouping in the AddNodePanel UI."},"NodeExample":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"additionalProperties":false,"type":"object","required":["name","data"],"title":"NodeExample","description":"A worked example LLMs can pattern-match. Keep small and realistic."},"NodeSpec":{"properties":{"name":{"type":"string","title":"Name"},"display_name":{"type":"string","title":"Display Name"},"description":{"type":"string","minLength":1,"title":"Description","description":"Human-facing explanation shown in AddNodePanel."},"llm_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Hint","description":"LLM-only guidance; omitted from the UI."},"category":{"$ref":"#/components/schemas/NodeCategory"},"icon":{"type":"string","title":"Icon"},"version":{"type":"string","title":"Version","default":"1.0.0"},"properties":{"items":{"$ref":"#/components/schemas/PropertySpec"},"type":"array","title":"Properties"},"examples":{"items":{"$ref":"#/components/schemas/NodeExample"},"type":"array","title":"Examples"},"graph_constraints":{"anyOf":[{"$ref":"#/components/schemas/GraphConstraints"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["name","display_name","description","category","icon","properties"],"title":"NodeSpec","description":"Single source of truth for a node type."},"NodeTypesResponse":{"properties":{"spec_version":{"type":"string","title":"Spec Version"},"node_types":{"items":{"$ref":"#/components/schemas/NodeSpec"},"type":"array","title":"Node Types"}},"type":"object","required":["spec_version","node_types"],"title":"NodeTypesResponse"},"PhoneNumberCreateRequest":{"properties":{"address":{"type":"string","maxLength":255,"minLength":1,"title":"Address"},"country_code":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country Code"},"label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Label"},"inbound_workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inbound Workflow Id"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_default_caller_id":{"type":"boolean","title":"Is Default Caller Id","default":false},"extra_metadata":{"additionalProperties":true,"type":"object","title":"Extra Metadata"}},"type":"object","required":["address"],"title":"PhoneNumberCreateRequest","description":"Create a new phone number under a telephony configuration.\n\n``address_normalized`` and ``address_type`` are computed server-side from\n``address`` (and ``country_code`` if PSTN). ``address`` itself is stored\nverbatim for display."},"PhoneNumberListResponse":{"properties":{"phone_numbers":{"items":{"$ref":"#/components/schemas/PhoneNumberResponse"},"type":"array","title":"Phone Numbers"}},"type":"object","required":["phone_numbers"],"title":"PhoneNumberListResponse"},"PhoneNumberResponse":{"properties":{"id":{"type":"integer","title":"Id"},"telephony_configuration_id":{"type":"integer","title":"Telephony Configuration Id"},"address":{"type":"string","title":"Address"},"address_normalized":{"type":"string","title":"Address Normalized"},"address_type":{"type":"string","title":"Address Type"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"inbound_workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inbound Workflow Id"},"inbound_workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inbound Workflow Name"},"is_active":{"type":"boolean","title":"Is Active"},"is_default_caller_id":{"type":"boolean","title":"Is Default Caller Id"},"extra_metadata":{"additionalProperties":true,"type":"object","title":"Extra Metadata"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"provider_sync":{"anyOf":[{"$ref":"#/components/schemas/ProviderSyncStatus"},{"type":"null"}]}},"type":"object","required":["id","telephony_configuration_id","address","address_normalized","address_type","is_active","is_default_caller_id","extra_metadata","created_at","updated_at"],"title":"PhoneNumberResponse"},"PhoneNumberUpdateRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Label"},"inbound_workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inbound Workflow Id"},"clear_inbound_workflow":{"type":"boolean","title":"Clear Inbound Workflow","default":false},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"country_code":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country Code"},"extra_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Extra Metadata"}},"type":"object","title":"PhoneNumberUpdateRequest","description":"Partial update. ``address`` is intentionally immutable \u2014 to change a\nnumber, delete the row and create a new one."},"PlivoConfigurationRequest":{"properties":{"provider":{"type":"string","const":"plivo","title":"Provider","default":"plivo"},"auth_id":{"type":"string","title":"Auth Id","description":"Plivo Auth ID"},"auth_token":{"type":"string","title":"Auth Token","description":"Plivo Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id","description":"Plivo Application ID. The application's answer_url is updated when inbound workflows are attached to numbers on this account. If omitted, an application is auto-created on save and its id is stored on the configuration."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Plivo phone numbers"}},"type":"object","required":["auth_id","auth_token"],"title":"PlivoConfigurationRequest","description":"Request schema for Plivo configuration."},"PlivoConfigurationResponse":{"properties":{"provider":{"type":"string","const":"plivo","title":"Provider","default":"plivo"},"auth_id":{"type":"string","title":"Auth Id"},"auth_token":{"type":"string","title":"Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["auth_id","auth_token","from_numbers"],"title":"PlivoConfigurationResponse","description":"Response schema for Plivo configuration with masked sensitive fields."},"PresetToolParameter":{"properties":{"name":{"type":"string","title":"Name","description":"Parameter name (used as key in request body)"},"type":{"type":"string","title":"Type","description":"Parameter type: string, number, or boolean"},"value_template":{"type":"string","title":"Value Template","description":"Fixed value or template, e.g. {{initial_context.phone_number}}"},"required":{"type":"boolean","title":"Required","description":"Whether the parameter must resolve to a non-empty value","default":true}},"type":"object","required":["name","type","value_template"],"title":"PresetToolParameter","description":"A parameter injected by Dograh at runtime."},"PresignedUploadUrlRequest":{"properties":{"file_name":{"type":"string","pattern":".*\\.csv$","title":"File Name","description":"CSV filename"},"file_size":{"type":"integer","maximum":10485760.0,"exclusiveMinimum":0.0,"title":"File Size","description":"File size in bytes (max 10MB)"},"content_type":{"type":"string","title":"Content Type","description":"File content type","default":"text/csv"}},"type":"object","required":["file_name","file_size"],"title":"PresignedUploadUrlRequest"},"PresignedUploadUrlResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"file_key":{"type":"string","title":"File Key"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["upload_url","file_key","expires_in"],"title":"PresignedUploadUrlResponse"},"ProcessDocumentRequestSchema":{"properties":{"document_uuid":{"type":"string","title":"Document Uuid","description":"Document UUID to process"},"s3_key":{"type":"string","title":"S3 Key","description":"S3 key of the uploaded file"},"retrieval_mode":{"type":"string","title":"Retrieval Mode","description":"Retrieval mode: 'chunked' for vector search or 'full_document' for full text retrieval","default":"chunked"}},"type":"object","required":["document_uuid","s3_key"],"title":"ProcessDocumentRequestSchema","description":"Request schema for triggering document processing."},"PropertyOption":{"properties":{"value":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"boolean"},{"type":"number"}],"title":"Value"},"label":{"type":"string","title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"additionalProperties":false,"type":"object","required":["value","label"],"title":"PropertyOption","description":"An option in an `options` or `multi_options` dropdown."},"PropertySpec":{"properties":{"name":{"type":"string","title":"Name"},"type":{"$ref":"#/components/schemas/PropertyType"},"display_name":{"type":"string","title":"Display Name"},"description":{"type":"string","minLength":1,"title":"Description","description":"Human-facing explanation shown in the UI."},"llm_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Hint","description":"LLM-only guidance; omitted from the UI."},"default":{"title":"Default"},"required":{"type":"boolean","title":"Required","default":false},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder"},"display_options":{"anyOf":[{"$ref":"#/components/schemas/DisplayOptions"},{"type":"null"}]},"options":{"anyOf":[{"items":{"$ref":"#/components/schemas/PropertyOption"},"type":"array"},{"type":"null"}],"title":"Options"},"properties":{"anyOf":[{"items":{"$ref":"#/components/schemas/PropertySpec"},"type":"array"},{"type":"null"}],"title":"Properties"},"min_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Value"},"max_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Value"},"min_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Length"},"max_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Length"},"pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pattern"},"editor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editor"},"extra":{"additionalProperties":true,"type":"object","title":"Extra"}},"additionalProperties":false,"type":"object","required":["name","type","display_name","description"],"title":"PropertySpec","description":"Single field on a node.\n\n`description` is HUMAN-FACING \u2014 shown under the field in the edit\ndialog. Keep it concise and explain what the field does.\n\n`llm_hint` is LLM-FACING \u2014 appears only in the `get_node_type` MCP\nresponse and in SDK schema output. Use it for catalog tool references\n(e.g., \"Use `list_recordings`\"), array shape, expected value idioms,\nor anything that would be noise in the UI. Optional; omit when the\n`description` already suffices for both audiences."},"PropertyType":{"type":"string","enum":["string","number","boolean","options","multi_options","fixed_collection","json","tool_refs","document_refs","recording_ref","credential_ref","mention_textarea","url"],"title":"PropertyType","description":"Bounded vocabulary of property types the renderer dispatches on.\n\nAdding a value here requires a matching arm in the frontend\n`` switch and (where relevant) the SDK codegen template."},"ProviderSyncStatus":{"properties":{"ok":{"type":"boolean","title":"Ok"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["ok"],"title":"ProviderSyncStatus","description":"Result of pushing a phone-number change to the upstream provider.\n\nReturned alongside create/update responses when the route attempted to\nsync inbound webhook configuration. ``ok=False`` is a warning, not a\nfatal error \u2014 the DB write succeeded."},"RecordingCreateRequestSchema":{"properties":{"recording_id":{"type":"string","title":"Recording Id","description":"Short recording ID from upload step"},"tts_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Provider","description":"TTS provider (e.g. elevenlabs)"},"tts_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Model","description":"TTS model name"},"tts_voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Voice Id","description":"TTS voice identifier"},"transcript":{"type":"string","title":"Transcript","description":"User-provided transcript of the recording"},"storage_key":{"type":"string","title":"Storage Key","description":"Storage key from upload step"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Optional metadata (file_size, duration, etc.)"}},"type":"object","required":["recording_id","transcript","storage_key"],"title":"RecordingCreateRequestSchema","description":"Request schema for creating a recording record after upload."},"RecordingListResponseSchema":{"properties":{"recordings":{"items":{"$ref":"#/components/schemas/RecordingResponseSchema"},"type":"array","title":"Recordings"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["recordings","total"],"title":"RecordingListResponseSchema","description":"Response schema for list of recordings."},"RecordingResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"recording_id":{"type":"string","title":"Recording Id"},"workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Id"},"organization_id":{"type":"integer","title":"Organization Id"},"tts_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Provider"},"tts_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Model"},"tts_voice_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tts Voice Id"},"transcript":{"type":"string","title":"Transcript"},"storage_key":{"type":"string","title":"Storage Key"},"storage_backend":{"type":"string","title":"Storage Backend"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"created_by":{"type":"integer","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","recording_id","organization_id","transcript","storage_key","storage_backend","metadata","created_by","created_at","is_active"],"title":"RecordingResponseSchema","description":"Response schema for a single recording."},"RecordingUpdateRequestSchema":{"properties":{"recording_id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","title":"Recording Id","description":"New descriptive recording ID (letters, numbers, hyphens, underscores only)"}},"type":"object","required":["recording_id"],"title":"RecordingUpdateRequestSchema","description":"Request schema for updating a recording's ID."},"RecordingUploadResponseSchema":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Presigned URL for uploading the audio"},"recording_id":{"type":"string","title":"Recording Id","description":"Short unique recording ID"},"storage_key":{"type":"string","title":"Storage Key","description":"Storage key where file will be uploaded"}},"type":"object","required":["upload_url","recording_id","storage_key"],"title":"RecordingUploadResponseSchema","description":"Response schema with presigned upload URL."},"RedialCampaignRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name","description":"Name for the redial campaign"},"retry_on_voicemail":{"type":"boolean","title":"Retry On Voicemail","default":true},"retry_on_no_answer":{"type":"boolean","title":"Retry On No Answer","default":true},"retry_on_busy":{"type":"boolean","title":"Retry On Busy","default":true},"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigRequest"},{"type":"null"}]}},"type":"object","title":"RedialCampaignRequest"},"RetryConfigRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"max_retries":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Max Retries","default":2},"retry_delay_seconds":{"type":"integer","maximum":3600.0,"minimum":30.0,"title":"Retry Delay Seconds","default":120},"retry_on_busy":{"type":"boolean","title":"Retry On Busy","default":true},"retry_on_no_answer":{"type":"boolean","title":"Retry On No Answer","default":true},"retry_on_voicemail":{"type":"boolean","title":"Retry On Voicemail","default":true}},"type":"object","title":"RetryConfigRequest"},"RetryConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"max_retries":{"type":"integer","title":"Max Retries"},"retry_delay_seconds":{"type":"integer","title":"Retry Delay Seconds"},"retry_on_busy":{"type":"boolean","title":"Retry On Busy"},"retry_on_no_answer":{"type":"boolean","title":"Retry On No Answer"},"retry_on_voicemail":{"type":"boolean","title":"Retry On Voicemail"}},"type":"object","required":["enabled","max_retries","retry_delay_seconds","retry_on_busy","retry_on_no_answer","retry_on_voicemail"],"title":"RetryConfigResponse"},"S3SignedUrlResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["url","expires_in"],"title":"S3SignedUrlResponse"},"ScheduleConfigRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"timezone":{"type":"string","title":"Timezone","default":"UTC"},"slots":{"items":{"$ref":"#/components/schemas/TimeSlotRequest"},"type":"array","maxItems":50,"minItems":1,"title":"Slots"}},"type":"object","required":["slots"],"title":"ScheduleConfigRequest"},"ScheduleConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"timezone":{"type":"string","title":"Timezone"},"slots":{"items":{"$ref":"#/components/schemas/TimeSlotResponse"},"type":"array","title":"Slots"}},"type":"object","required":["enabled","timezone","slots"],"title":"ScheduleConfigResponse"},"ServiceKeyResponse":{"properties":{"name":{"type":"string","title":"Name"},"id":{"type":"integer","title":"Id"},"key_prefix":{"type":"string","title":"Key Prefix"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"archived_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Archived At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["name","id","key_prefix","is_active","created_at"],"title":"ServiceKeyResponse"},"SignupRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["email","password"],"title":"SignupRequest"},"SuperuserWorkflowRunResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name"},"user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"},"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"organization_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Name"},"mode":{"type":"string","title":"Mode"},"is_completed":{"type":"boolean","title":"Is Completed"},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url"},"transcript_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Url"},"usage_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Usage Info"},"cost_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cost Info"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","workflow_id","workflow_name","user_id","organization_id","organization_name","mode","is_completed","recording_url","transcript_url","usage_info","cost_info","initial_context","gathered_context","created_at"],"title":"SuperuserWorkflowRunResponse"},"SuperuserWorkflowRunsListResponse":{"properties":{"workflow_runs":{"items":{"$ref":"#/components/schemas/SuperuserWorkflowRunResponse"},"type":"array","title":"Workflow Runs"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["workflow_runs","total_count","page","limit","total_pages"],"title":"SuperuserWorkflowRunsListResponse"},"TelephonyConfigWarningsResponse":{"properties":{"telnyx_missing_webhook_public_key_count":{"type":"integer","title":"Telnyx Missing Webhook Public Key Count"}},"type":"object","required":["telnyx_missing_webhook_public_key_count"],"title":"TelephonyConfigWarningsResponse","description":"Aggregated telephony-configuration warning counts for the user's org.\n\nDrives the page banner and nav badge that nudge customers to finish\noptional-but-recommended configuration steps. Shape is a flat dict so\nnew warning types can be added without breaking the client."},"TelephonyConfigurationCreateRequest":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name"},"is_default_outbound":{"type":"boolean","title":"Is Default Outbound","default":false},"config":{"oneOf":[{"$ref":"#/components/schemas/ARIConfigurationRequest"},{"$ref":"#/components/schemas/CloudonixConfigurationRequest"},{"$ref":"#/components/schemas/PlivoConfigurationRequest"},{"$ref":"#/components/schemas/TelnyxConfigurationRequest"},{"$ref":"#/components/schemas/TwilioConfigurationRequest"},{"$ref":"#/components/schemas/VobizConfigurationRequest"},{"$ref":"#/components/schemas/VonageConfigurationRequest"}],"title":"Config","discriminator":{"propertyName":"provider","mapping":{"ari":"#/components/schemas/ARIConfigurationRequest","cloudonix":"#/components/schemas/CloudonixConfigurationRequest","plivo":"#/components/schemas/PlivoConfigurationRequest","telnyx":"#/components/schemas/TelnyxConfigurationRequest","twilio":"#/components/schemas/TwilioConfigurationRequest","vobiz":"#/components/schemas/VobizConfigurationRequest","vonage":"#/components/schemas/VonageConfigurationRequest"}}}},"type":"object","required":["name","config"],"title":"TelephonyConfigurationCreateRequest","description":"Body for ``POST /telephony-configs``.\n\n``config`` carries the provider-specific credential fields (the same\ndiscriminated union used by the legacy single-config endpoint). Any\n``from_numbers`` on the inner config are ignored \u2014 phone numbers are\nmanaged via the dedicated phone-numbers endpoints."},"TelephonyConfigurationDetail":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"provider":{"type":"string","title":"Provider"},"is_default_outbound":{"type":"boolean","title":"Is Default Outbound"},"credentials":{"additionalProperties":true,"type":"object","title":"Credentials"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","provider","is_default_outbound","credentials","created_at","updated_at"],"title":"TelephonyConfigurationDetail","description":"Body of ``GET /telephony-configs/{id}`` \u2014 credentials are masked."},"TelephonyConfigurationListItem":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"provider":{"type":"string","title":"Provider"},"is_default_outbound":{"type":"boolean","title":"Is Default Outbound"},"phone_number_count":{"type":"integer","title":"Phone Number Count","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","provider","is_default_outbound","created_at","updated_at"],"title":"TelephonyConfigurationListItem","description":"One row in ``GET /telephony-configs``."},"TelephonyConfigurationListResponse":{"properties":{"configurations":{"items":{"$ref":"#/components/schemas/TelephonyConfigurationListItem"},"type":"array","title":"Configurations"}},"type":"object","required":["configurations"],"title":"TelephonyConfigurationListResponse"},"TelephonyConfigurationResponse":{"properties":{"twilio":{"anyOf":[{"$ref":"#/components/schemas/TwilioConfigurationResponse"},{"type":"null"}]},"plivo":{"anyOf":[{"$ref":"#/components/schemas/PlivoConfigurationResponse"},{"type":"null"}]},"vonage":{"anyOf":[{"$ref":"#/components/schemas/VonageConfigurationResponse"},{"type":"null"}]},"vobiz":{"anyOf":[{"$ref":"#/components/schemas/VobizConfigurationResponse"},{"type":"null"}]},"cloudonix":{"anyOf":[{"$ref":"#/components/schemas/CloudonixConfigurationResponse"},{"type":"null"}]},"ari":{"anyOf":[{"$ref":"#/components/schemas/ARIConfigurationResponse"},{"type":"null"}]},"telnyx":{"anyOf":[{"$ref":"#/components/schemas/TelnyxConfigurationResponse"},{"type":"null"}]}},"type":"object","title":"TelephonyConfigurationResponse","description":"Top-level telephony configuration response.\n\nKeeps the per-provider field shape that the UI client depends on. When\nthe UI moves to metadata-driven forms, this can be replaced with a\nflat discriminated union."},"TelephonyConfigurationUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Name"},"config":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/ARIConfigurationRequest"},{"$ref":"#/components/schemas/CloudonixConfigurationRequest"},{"$ref":"#/components/schemas/PlivoConfigurationRequest"},{"$ref":"#/components/schemas/TelnyxConfigurationRequest"},{"$ref":"#/components/schemas/TwilioConfigurationRequest"},{"$ref":"#/components/schemas/VobizConfigurationRequest"},{"$ref":"#/components/schemas/VonageConfigurationRequest"}],"discriminator":{"propertyName":"provider","mapping":{"ari":"#/components/schemas/ARIConfigurationRequest","cloudonix":"#/components/schemas/CloudonixConfigurationRequest","plivo":"#/components/schemas/PlivoConfigurationRequest","telnyx":"#/components/schemas/TelnyxConfigurationRequest","twilio":"#/components/schemas/TwilioConfigurationRequest","vobiz":"#/components/schemas/VobizConfigurationRequest","vonage":"#/components/schemas/VonageConfigurationRequest"}}},{"type":"null"}],"title":"Config"}},"type":"object","title":"TelephonyConfigurationUpdateRequest","description":"Body for ``PUT /telephony-configs/{id}``. Partial update."},"TelephonyProviderMetadata":{"properties":{"provider":{"type":"string","title":"Provider"},"display_name":{"type":"string","title":"Display Name"},"fields":{"items":{"$ref":"#/components/schemas/TelephonyProviderUIField"},"type":"array","title":"Fields"},"docs_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Docs Url"}},"type":"object","required":["provider","display_name","fields"],"title":"TelephonyProviderMetadata","description":"UI form metadata for a single telephony provider."},"TelephonyProviderUIField":{"properties":{"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type"},"required":{"type":"boolean","title":"Required"},"sensitive":{"type":"boolean","title":"Sensitive"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder"}},"type":"object","required":["name","label","type","required","sensitive"],"title":"TelephonyProviderUIField","description":"One form field on a telephony provider's configuration UI."},"TelephonyProvidersMetadataResponse":{"properties":{"providers":{"items":{"$ref":"#/components/schemas/TelephonyProviderMetadata"},"type":"array","title":"Providers"}},"type":"object","required":["providers"],"title":"TelephonyProvidersMetadataResponse","description":"List of UI form definitions used by the telephony-config screen."},"TelnyxConfigurationRequest":{"properties":{"provider":{"type":"string","const":"telnyx","title":"Provider","default":"telnyx"},"api_key":{"type":"string","title":"Api Key","description":"Telnyx API Key"},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id","description":"Telnyx Call Control Application ID (connection_id). If omitted, a Call Control Application is auto-created on save and its id is stored on the configuration."},"webhook_public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Public Key","description":"Webhook public key from Mission Control Portal \u2192 Keys & Credentials \u2192 Public Key. Used to verify Telnyx webhook signatures."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Telnyx phone numbers"}},"type":"object","required":["api_key"],"title":"TelnyxConfigurationRequest","description":"Request schema for Telnyx configuration."},"TelnyxConfigurationResponse":{"properties":{"provider":{"type":"string","const":"telnyx","title":"Provider","default":"telnyx"},"api_key":{"type":"string","title":"Api Key"},"connection_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connection Id"},"webhook_public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Public Key"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["api_key","from_numbers"],"title":"TelnyxConfigurationResponse","description":"Response schema for Telnyx configuration with masked sensitive fields."},"TimeSlotRequest":{"properties":{"day_of_week":{"type":"integer","maximum":6.0,"minimum":0.0,"title":"Day Of Week"},"start_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","title":"Start Time"},"end_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","title":"End Time"}},"type":"object","required":["day_of_week","start_time","end_time"],"title":"TimeSlotRequest"},"TimeSlotResponse":{"properties":{"day_of_week":{"type":"integer","title":"Day Of Week"},"start_time":{"type":"string","title":"Start Time"},"end_time":{"type":"string","title":"End Time"}},"type":"object","required":["day_of_week","start_time","end_time"],"title":"TimeSlotResponse"},"ToolParameter":{"properties":{"name":{"type":"string","title":"Name","description":"Parameter name (used as key in request body)"},"type":{"type":"string","title":"Type","description":"Parameter type: string, number, or boolean"},"description":{"type":"string","title":"Description","description":"Description of what this parameter is for"},"required":{"type":"boolean","title":"Required","description":"Whether this parameter is required","default":true}},"type":"object","required":["name","type","description"],"title":"ToolParameter","description":"A parameter that the tool accepts."},"ToolResponse":{"properties":{"id":{"type":"integer","title":"Id"},"tool_uuid":{"type":"string","title":"Tool Uuid"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"type":"string","title":"Category"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"icon_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon Color"},"status":{"type":"string","title":"Status"},"definition":{"additionalProperties":true,"type":"object","title":"Definition"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"created_by":{"anyOf":[{"$ref":"#/components/schemas/CreatedByResponse"},{"type":"null"}]}},"type":"object","required":["id","tool_uuid","name","description","category","icon","icon_color","status","definition","created_at","updated_at"],"title":"ToolResponse","description":"Response schema for a tool."},"TransferCallConfig":{"properties":{"destination":{"type":"string","title":"Destination","description":"Phone number or SIP endpoint to transfer the call to (E.164 format e.g., +1234567890, or SIP endpoint e.g., PJSIP/1234)"},"messageType":{"type":"string","enum":["none","custom","audio"],"title":"Messagetype","description":"Type of message to play before transfer","default":"none"},"customMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessage","description":"Custom message to play before transferring the call"},"audioRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audiorecordingid","description":"Recording ID for audio message before transfer"},"timeout":{"type":"integer","maximum":120.0,"minimum":5.0,"title":"Timeout","description":"Maximum time in seconds to wait for destination to answer (5-120 seconds)","default":30}},"type":"object","required":["destination"],"title":"TransferCallConfig","description":"Configuration for Transfer Call tools."},"TransferCallToolDefinition":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Schema version","default":1},"type":{"type":"string","const":"transfer_call","title":"Type","description":"Tool type"},"config":{"$ref":"#/components/schemas/TransferCallConfig","description":"Transfer Call configuration"}},"type":"object","required":["type","config"],"title":"TransferCallToolDefinition","description":"Tool definition for Transfer Call tools."},"TriggerCallRequest":{"properties":{"phone_number":{"type":"string","title":"Phone Number"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"telephony_configuration_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Telephony Configuration Id"}},"type":"object","required":["phone_number"],"title":"TriggerCallRequest","description":"Request model for triggering a call via API"},"TriggerCallResponse":{"properties":{"status":{"type":"string","title":"Status"},"workflow_run_id":{"type":"integer","title":"Workflow Run Id"},"workflow_run_name":{"type":"string","title":"Workflow Run Name"}},"type":"object","required":["status","workflow_run_id","workflow_run_name"],"title":"TriggerCallResponse","description":"Response model for successful call initiation"},"TurnCredentialsResponse":{"properties":{"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"ttl":{"type":"integer","title":"Ttl"},"uris":{"items":{"type":"string"},"type":"array","title":"Uris"}},"type":"object","required":["username","password","ttl","uris"],"title":"TurnCredentialsResponse","description":"Response model for TURN credentials."},"TwilioConfigurationRequest":{"properties":{"provider":{"type":"string","const":"twilio","title":"Provider","default":"twilio"},"account_sid":{"type":"string","title":"Account Sid","description":"Twilio Account SID"},"auth_token":{"type":"string","title":"Auth Token","description":"Twilio Auth Token"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Twilio phone numbers"}},"type":"object","required":["account_sid","auth_token"],"title":"TwilioConfigurationRequest","description":"Request schema for Twilio configuration."},"TwilioConfigurationResponse":{"properties":{"provider":{"type":"string","const":"twilio","title":"Provider","default":"twilio"},"account_sid":{"type":"string","title":"Account Sid"},"auth_token":{"type":"string","title":"Auth Token"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["account_sid","auth_token","from_numbers"],"title":"TwilioConfigurationResponse","description":"Response schema for Twilio configuration with masked sensitive fields."},"UpdateCampaignRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"retry_config":{"anyOf":[{"$ref":"#/components/schemas/RetryConfigRequest"},{"type":"null"}]},"max_concurrency":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":1.0},{"type":"null"}],"title":"Max Concurrency"},"schedule_config":{"anyOf":[{"$ref":"#/components/schemas/ScheduleConfigRequest"},{"type":"null"}]},"circuit_breaker":{"anyOf":[{"$ref":"#/components/schemas/CircuitBreakerConfigRequest"},{"type":"null"}]}},"type":"object","title":"UpdateCampaignRequest"},"UpdateCredentialRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"credential_type":{"anyOf":[{"$ref":"#/components/schemas/WebhookCredentialType"},{"type":"null"}]},"credential_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Credential Data"}},"type":"object","title":"UpdateCredentialRequest","description":"Request schema for updating a webhook credential."},"UpdateToolRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"icon":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Icon"},"icon_color":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Icon Color"},"definition":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/HttpApiToolDefinition"},{"$ref":"#/components/schemas/EndCallToolDefinition"},{"$ref":"#/components/schemas/TransferCallToolDefinition"},{"$ref":"#/components/schemas/CalculatorToolDefinition"},{"$ref":"#/components/schemas/McpToolDefinition"}],"discriminator":{"propertyName":"type","mapping":{"calculator":"#/components/schemas/CalculatorToolDefinition","end_call":"#/components/schemas/EndCallToolDefinition","http_api":"#/components/schemas/HttpApiToolDefinition","mcp":"#/components/schemas/McpToolDefinition","transfer_call":"#/components/schemas/TransferCallToolDefinition"}}},{"type":"null"}],"title":"Definition"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"UpdateToolRequest","description":"Request schema for updating a tool."},"UpdateWorkflowRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"workflow_definition":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Definition"},"template_context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Context Variables"},"workflow_configurations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Configurations"}},"type":"object","title":"UpdateWorkflowRequest"},"UpdateWorkflowStatusRequest":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"UpdateWorkflowStatusRequest"},"UsageHistoryResponse":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/WorkflowRunUsageResponse"},"type":"array","title":"Runs"},"total_dograh_tokens":{"type":"number","title":"Total Dograh Tokens"},"total_duration_seconds":{"type":"integer","title":"Total Duration Seconds"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["runs","total_dograh_tokens","total_duration_seconds","total_count","page","limit","total_pages"],"title":"UsageHistoryResponse"},"UserConfigurationRequestResponseSchema":{"properties":{"llm":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Llm"},"tts":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Tts"},"stt":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Stt"},"embeddings":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Embeddings"},"realtime":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"type":"object"},{"type":"null"}],"title":"Realtime"},"is_realtime":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Realtime"},"test_phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Phone Number"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"organization_pricing":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"boolean"}]},"type":"object"},{"type":"null"}],"title":"Organization Pricing"}},"type":"object","title":"UserConfigurationRequestResponseSchema"},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"}},"type":"object","required":["id","email"],"title":"UserResponse"},"ValidateWorkflowResponse":{"properties":{"is_valid":{"type":"boolean","title":"Is Valid"},"errors":{"items":{"$ref":"#/components/schemas/WorkflowError"},"type":"array","title":"Errors"}},"type":"object","required":["is_valid","errors"],"title":"ValidateWorkflowResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VobizConfigurationRequest":{"properties":{"provider":{"type":"string","const":"vobiz","title":"Provider","default":"vobiz"},"auth_id":{"type":"string","title":"Auth Id","description":"Vobiz Account ID (e.g., MA_SYQRLN1K)"},"auth_token":{"type":"string","title":"Auth Token","description":"Vobiz Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id","description":"Vobiz Application ID. The application's answer_url is updated when inbound workflows are attached to numbers on this account. If omitted, an application is auto-created on save and its id is stored on the configuration."},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Vobiz phone numbers (E.164 without + prefix)"}},"type":"object","required":["auth_id","auth_token"],"title":"VobizConfigurationRequest","description":"Request schema for Vobiz configuration."},"VobizConfigurationResponse":{"properties":{"provider":{"type":"string","const":"vobiz","title":"Provider","default":"vobiz"},"auth_id":{"type":"string","title":"Auth Id"},"auth_token":{"type":"string","title":"Auth Token"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["auth_id","auth_token","from_numbers"],"title":"VobizConfigurationResponse","description":"Response schema for Vobiz configuration with masked sensitive fields."},"VoiceInfo":{"properties":{"voice_id":{"type":"string","title":"Voice Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"accent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url"}},"type":"object","required":["voice_id","name"],"title":"VoiceInfo"},"VoicesResponse":{"properties":{"provider":{"type":"string","title":"Provider"},"voices":{"items":{"$ref":"#/components/schemas/VoiceInfo"},"type":"array","title":"Voices"}},"type":"object","required":["provider","voices"],"title":"VoicesResponse"},"VonageConfigurationRequest":{"properties":{"provider":{"type":"string","const":"vonage","title":"Provider","default":"vonage"},"api_key":{"type":"string","title":"Api Key","description":"Vonage API Key"},"api_secret":{"type":"string","title":"Api Secret","description":"Vonage API Secret"},"application_id":{"type":"string","title":"Application Id","description":"Vonage Application ID"},"private_key":{"type":"string","title":"Private Key","description":"Private key for JWT generation"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers","description":"List of Vonage phone numbers (without + prefix)"}},"type":"object","required":["api_key","api_secret","application_id","private_key"],"title":"VonageConfigurationRequest","description":"Request schema for Vonage configuration."},"VonageConfigurationResponse":{"properties":{"provider":{"type":"string","const":"vonage","title":"Provider","default":"vonage"},"application_id":{"type":"string","title":"Application Id"},"api_key":{"type":"string","title":"Api Key"},"api_secret":{"type":"string","title":"Api Secret"},"private_key":{"type":"string","title":"Private Key"},"from_numbers":{"items":{"type":"string"},"type":"array","title":"From Numbers"}},"type":"object","required":["application_id","api_key","api_secret","private_key","from_numbers"],"title":"VonageConfigurationResponse","description":"Response schema for Vonage configuration with masked sensitive fields."},"WebhookCredentialType":{"type":"string","enum":["none","api_key","bearer_token","basic_auth","custom_header"],"title":"WebhookCredentialType","description":"Webhook credential authentication types"},"WorkflowCountResponse":{"properties":{"total":{"type":"integer","title":"Total"},"active":{"type":"integer","title":"Active"},"archived":{"type":"integer","title":"Archived"}},"type":"object","required":["total","active","archived"],"title":"WorkflowCountResponse","description":"Response for workflow count endpoint."},"WorkflowError":{"properties":{"kind":{"$ref":"#/components/schemas/ItemKind"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field"},"message":{"type":"string","title":"Message"}},"type":"object","required":["kind","id","field","message"],"title":"WorkflowError"},"WorkflowListResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"total_runs":{"type":"integer","title":"Total Runs"}},"type":"object","required":["id","name","status","created_at","total_runs"],"title":"WorkflowListResponse","description":"Lightweight response for workflow listings (excludes large fields)."},"WorkflowOption":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"WorkflowOption"},"WorkflowResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"workflow_definition":{"additionalProperties":true,"type":"object","title":"Workflow Definition"},"current_definition_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Current Definition Id"},"template_context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Context Variables"},"call_disposition_codes":{"anyOf":[{"$ref":"#/components/schemas/CallDispositionCodes"},{"type":"null"}]},"total_runs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Runs"},"workflow_configurations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Configurations"},"version_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Version Number"},"version_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version Status"},"workflow_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Uuid"}},"type":"object","required":["id","name","status","created_at","workflow_definition","current_definition_id"],"title":"WorkflowResponse"},"WorkflowRunDetail":{"properties":{"phone_number":{"type":"string","title":"Phone Number"},"disposition":{"type":"string","title":"Disposition"},"duration_seconds":{"type":"number","title":"Duration Seconds"},"workflow_id":{"type":"integer","title":"Workflow Id"},"run_id":{"type":"integer","title":"Run Id"},"workflow_name":{"type":"string","title":"Workflow Name"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["phone_number","disposition","duration_seconds","workflow_id","run_id","workflow_name","created_at"],"title":"WorkflowRunDetail"},"WorkflowRunResponseSchema":{"properties":{"id":{"type":"integer","title":"Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"name":{"type":"string","title":"Name"},"mode":{"type":"string","title":"Mode"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_completed":{"type":"boolean","title":"Is Completed"},"transcript_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Url"},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url"},"cost_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cost Info"},"definition_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Definition Id"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"call_type":{"$ref":"#/components/schemas/CallType"},"logs":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Logs"},"annotations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annotations"}},"type":"object","required":["id","workflow_id","name","mode","created_at","is_completed","transcript_url","recording_url","cost_info","definition_id","call_type"],"title":"WorkflowRunResponseSchema"},"WorkflowRunUsageResponse":{"properties":{"id":{"type":"integer","title":"Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"workflow_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","title":"Created At"},"dograh_token_usage":{"type":"number","title":"Dograh Token Usage"},"call_duration_seconds":{"type":"integer","title":"Call Duration Seconds"},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url"},"transcript_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Url"},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number","description":"Deprecated. Use caller_number and called_number instead.","deprecated":true},"caller_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caller Number"},"called_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Called Number"},"call_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Call Type"},"disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"charge_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Charge Usd"}},"type":"object","required":["id","workflow_id","workflow_name","name","created_at","dograh_token_usage","call_duration_seconds"],"title":"WorkflowRunUsageResponse"},"WorkflowRunsResponse":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/WorkflowRunResponseSchema"},"type":"array","title":"Runs"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"},"total_pages":{"type":"integer","title":"Total Pages"},"applied_filters":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Applied Filters"}},"type":"object","required":["runs","total_count","page","limit","total_pages"],"title":"WorkflowRunsResponse"},"WorkflowSummaryResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"WorkflowSummaryResponse"},"WorkflowTemplateResponse":{"properties":{"id":{"type":"integer","title":"Id"},"template_name":{"type":"string","title":"Template Name"},"template_description":{"type":"string","title":"Template Description"},"template_json":{"additionalProperties":true,"type":"object","title":"Template Json"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","template_name","template_description","template_json","created_at"],"title":"WorkflowTemplateResponse"},"WorkflowVersionResponse":{"properties":{"id":{"type":"integer","title":"Id"},"version_number":{"type":"integer","title":"Version Number"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At"},"workflow_json":{"additionalProperties":true,"type":"object","title":"Workflow Json"},"workflow_configurations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Workflow Configurations"},"template_context_variables":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Context Variables"}},"type":"object","required":["id","version_number","status","created_at","workflow_json"],"title":"WorkflowVersionResponse"}}}} \ No newline at end of file diff --git a/scripts/generate_sdk.sh b/scripts/generate_sdk.sh index da69408..56043d8 100755 --- a/scripts/generate_sdk.sh +++ b/scripts/generate_sdk.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Regenerate every file the SDKs derive from authoritative backend state: # -# 1. Typed node dataclasses / TS interfaces (from node_specs registry) +# 1. Typed node dataclasses / TS interfaces (from the model-backed +# node-spec registry) # 2. Filtered OpenAPI spec (routes tagged via @sdk_expose) # 3. Pydantic request/response models + TS interfaces (datamodel-codegen # / openapi-typescript) @@ -18,8 +19,9 @@ # is a devDependency of sdk/typescript; `npm install` in that dir is # done for you if node_modules is missing. # -# Invoked manually after editing any NodeSpec or after adding/removing an -# `@sdk_expose` decorator. CI runs this and asserts the git diff is empty. +# Invoked manually after editing workflow node models / node-spec metadata +# or after adding/removing an `@sdk_expose` decorator. CI runs this and +# asserts the git diff is empty. set -euo pipefail diff --git a/scripts/setup_pipecat.sh b/scripts/setup_pipecat.sh new file mode 100755 index 0000000..04821b0 --- /dev/null +++ b/scripts/setup_pipecat.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Setup script for using pipecat as a git submodule + +# Get the project root directory (parent of scripts) +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DOGRAH_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$DOGRAH_DIR" + +echo "Setting up pipecat as a git submodule..." + +# Initialize and update submodules +echo "Initializing git submodules..." +git submodule update --init --recursive + +# Install other requirements first so pipecat submodule wins any version conflicts +echo "Installing dograh API requirements..." +pip install -r api/requirements.txt + +# Install pipecat from submodule last so it overrides any pipecat-ai pulled in by dependencies +echo "Installing pipecat dependencies..." +pip install -e ./pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb] + +echo "Setup complete! Pipecat is now available as a git submodule." \ No newline at end of file diff --git a/sdk/python/src/dograh_sdk/_generated_models.py b/sdk/python/src/dograh_sdk/_generated_models.py index a0cba2f..17a5fca 100644 --- a/sdk/python/src/dograh_sdk/_generated_models.py +++ b/sdk/python/src/dograh_sdk/_generated_models.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: -# filename: dograh-openapi-XXXXXX.json.VXwJZQpZrH -# timestamp: 2026-05-19T11:05:11+00:00 +# filename: dograh-openapi-XXXXXX.json.bGP2QR1Vrx +# timestamp: 2026-05-20T08:41:57+00:00 from __future__ import annotations diff --git a/sdk/python/src/dograh_sdk/codegen.py b/sdk/python/src/dograh_sdk/codegen.py index 9d5de7d..019b356 100644 --- a/sdk/python/src/dograh_sdk/codegen.py +++ b/sdk/python/src/dograh_sdk/codegen.py @@ -127,8 +127,8 @@ def _format_docstring(text: str, indent: int = 4) -> str: _FILE_HEADER = '''"""GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations diff --git a/sdk/python/src/dograh_sdk/typed/__init__.py b/sdk/python/src/dograh_sdk/typed/__init__.py index b5dcea3..f8811a9 100644 --- a/sdk/python/src/dograh_sdk/typed/__init__.py +++ b/sdk/python/src/dograh_sdk/typed/__init__.py @@ -10,6 +10,7 @@ from dograh_sdk.typed.global_node import GlobalNode from dograh_sdk.typed.qa import Qa from dograh_sdk.typed.start_call import StartCall from dograh_sdk.typed.trigger import Trigger +from dograh_sdk.typed.tuner import Tuner from dograh_sdk.typed.webhook import Webhook from dograh_sdk.typed._base import TypedNode @@ -20,6 +21,7 @@ __all__ = [ "Qa", "StartCall", "Trigger", + "Tuner", "TypedNode", "Webhook", ] diff --git a/sdk/python/src/dograh_sdk/typed/agent_node.py b/sdk/python/src/dograh_sdk/typed/agent_node.py index 3b8c21e..cdceb4b 100644 --- a/sdk/python/src/dograh_sdk/typed/agent_node.py +++ b/sdk/python/src/dograh_sdk/typed/agent_node.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations @@ -16,8 +16,8 @@ from dograh_sdk.typed._base import TypedNode @dataclass(kw_only=True) class AgentNode_Extraction_variablesRow: """ - Each entry declares one variable to capture from the conversation, with - its name, type, and per-variable hint. + Each entry declares one variable to capture, with its name, data type, + and extraction hint. """ name: str @@ -70,8 +70,7 @@ class AgentNode(TypedNode): extraction_enabled: bool = False """ - When true, runs an LLM extraction pass on transition out of this node to - capture variables from the conversation. + When true, runs an LLM extraction pass for this node. """ extraction_prompt: Optional[str] = None @@ -81,8 +80,8 @@ class AgentNode(TypedNode): extraction_variables: list[AgentNode_Extraction_variablesRow] = field(default_factory=list) """ - Each entry declares one variable to capture from the conversation, with - its name, type, and per-variable hint. + Each entry declares one variable to capture, with its name, data type, + and extraction hint. """ tool_uuids: list[str] = field(default_factory=list) diff --git a/sdk/python/src/dograh_sdk/typed/end_call.py b/sdk/python/src/dograh_sdk/typed/end_call.py index 737205c..3c50772 100644 --- a/sdk/python/src/dograh_sdk/typed/end_call.py +++ b/sdk/python/src/dograh_sdk/typed/end_call.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations @@ -26,11 +26,11 @@ class EndCall_Extraction_variablesRow: """ type: Literal['string', 'number', 'boolean'] = 'string' """ - The data type of the extracted value. + Data type of the extracted value. """ prompt: Optional[str] = None """ - Per-variable hint describing what to look for in the conversation. + Per-variable hint describing what to look for. """ @dataclass(kw_only=True) diff --git a/sdk/python/src/dograh_sdk/typed/global_node.py b/sdk/python/src/dograh_sdk/typed/global_node.py index 6453230..1cbf1b4 100644 --- a/sdk/python/src/dograh_sdk/typed/global_node.py +++ b/sdk/python/src/dograh_sdk/typed/global_node.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations diff --git a/sdk/python/src/dograh_sdk/typed/qa.py b/sdk/python/src/dograh_sdk/typed/qa.py index 339e2af..f4d6d04 100644 --- a/sdk/python/src/dograh_sdk/typed/qa.py +++ b/sdk/python/src/dograh_sdk/typed/qa.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations diff --git a/sdk/python/src/dograh_sdk/typed/start_call.py b/sdk/python/src/dograh_sdk/typed/start_call.py index b9e2562..33faae1 100644 --- a/sdk/python/src/dograh_sdk/typed/start_call.py +++ b/sdk/python/src/dograh_sdk/typed/start_call.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations @@ -17,7 +17,7 @@ from dograh_sdk.typed._base import TypedNode class StartCall_Extraction_variablesRow: """ Each entry declares one variable to capture, with its name, data type, - and per-variable extraction hint. + and extraction hint. """ name: str @@ -97,8 +97,7 @@ class StartCall(TypedNode): extraction_enabled: bool = False """ - When true, runs an LLM extraction pass on transition out of this node to - capture variables from the opening turn. + When true, runs an LLM extraction pass for this node. """ extraction_prompt: Optional[str] = None @@ -109,7 +108,7 @@ class StartCall(TypedNode): extraction_variables: list[StartCall_Extraction_variablesRow] = field(default_factory=list) """ Each entry declares one variable to capture, with its name, data type, - and per-variable extraction hint. + and extraction hint. """ tool_uuids: list[str] = field(default_factory=list) diff --git a/sdk/python/src/dograh_sdk/typed/trigger.py b/sdk/python/src/dograh_sdk/typed/trigger.py index 3a6be4b..62b9ee7 100644 --- a/sdk/python/src/dograh_sdk/typed/trigger.py +++ b/sdk/python/src/dograh_sdk/typed/trigger.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations diff --git a/sdk/python/src/dograh_sdk/typed/tuner.py b/sdk/python/src/dograh_sdk/typed/tuner.py new file mode 100644 index 0000000..331fa06 --- /dev/null +++ b/sdk/python/src/dograh_sdk/typed/tuner.py @@ -0,0 +1,50 @@ +"""GENERATED — do not edit by hand. + +Regenerate with `python -m dograh_sdk.codegen` against the target +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal, Optional + +from dograh_sdk.typed._base import TypedNode + + +@dataclass(kw_only=True) +class Tuner(TypedNode): + """ + Export the completed call to Tuner for Agent Observability LLM hint: + Tuner is a post-call observability export. It does not participate in + the conversation graph and should not be connected to other nodes. + """ + + type: ClassVar[str] = 'tuner' + + tuner_agent_id: str + """ + The agent identifier registered in your Tuner workspace. + """ + + tuner_workspace_id: float + """ + Your numeric Tuner workspace ID. + """ + + tuner_api_key: str + """ + Bearer token used when posting completed calls to Tuner. + """ + + name: str = 'Tuner' + """ + Short identifier for this Tuner export configuration. + """ + + tuner_enabled: bool = True + """ + When false, Dograh skips exporting this call to Tuner. + """ + diff --git a/sdk/python/src/dograh_sdk/typed/webhook.py b/sdk/python/src/dograh_sdk/typed/webhook.py index 993a74e..305c0e4 100644 --- a/sdk/python/src/dograh_sdk/typed/webhook.py +++ b/sdk/python/src/dograh_sdk/typed/webhook.py @@ -1,8 +1,8 @@ """GENERATED — do not edit by hand. Regenerate with `python -m dograh_sdk.codegen` against the target -Dograh backend. Source of truth: each node's NodeSpec in the backend's -`api/services/workflow/node_specs/` directory. +Dograh backend. Source of truth: the backend's model-backed node-spec +catalog served from `/api/v1/node-types`. """ from __future__ import annotations diff --git a/sdk/typescript/scripts/codegen.mts b/sdk/typescript/scripts/codegen.mts index 954b1c0..cbde7e3 100644 --- a/sdk/typescript/scripts/codegen.mts +++ b/sdk/typescript/scripts/codegen.mts @@ -127,8 +127,8 @@ function renderSpecFile(spec: NodeSpec): string { const header = `// GENERATED — do not edit by hand. // // Regenerate with \`npm run codegen\` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// \`api/services/workflow/node_specs/\` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from \`/api/v1/node-types\`. `; const nested: string[] = []; diff --git a/sdk/typescript/src/typed/agent-node.ts b/sdk/typescript/src/typed/agent-node.ts index 2fd05e8..ec969e9 100644 --- a/sdk/typescript/src/typed/agent-node.ts +++ b/sdk/typescript/src/typed/agent-node.ts @@ -1,11 +1,11 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** - * Each entry declares one variable to capture from the conversation, with its name, type, and per-variable hint. + * Each entry declares one variable to capture, with its name, data type, and extraction hint. */ export interface AgentNodeExtraction_variablesRow { /** @@ -46,7 +46,7 @@ export interface AgentNode { */ add_global_prompt?: boolean; /** - * When true, runs an LLM extraction pass on transition out of this node to capture variables from the conversation. + * When true, runs an LLM extraction pass for this node. */ extraction_enabled?: boolean; /** @@ -54,7 +54,7 @@ export interface AgentNode { */ extraction_prompt?: string; /** - * Each entry declares one variable to capture from the conversation, with its name, type, and per-variable hint. + * Each entry declares one variable to capture, with its name, data type, and extraction hint. */ extraction_variables?: Array; /** diff --git a/sdk/typescript/src/typed/end-call.ts b/sdk/typescript/src/typed/end-call.ts index 7d0aed6..3142792 100644 --- a/sdk/typescript/src/typed/end-call.ts +++ b/sdk/typescript/src/typed/end-call.ts @@ -1,8 +1,8 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** * Each entry declares one variable to capture from the conversation, with its name, data type, and a per-variable extraction hint. @@ -13,11 +13,11 @@ export interface EndCallExtraction_variablesRow { */ name: string; /** - * The data type of the extracted value. + * Data type of the extracted value. */ type: "string" | "number" | "boolean"; /** - * Per-variable hint describing what to look for in the conversation. + * Per-variable hint describing what to look for. */ prompt?: string; } diff --git a/sdk/typescript/src/typed/global-node.ts b/sdk/typescript/src/typed/global-node.ts index 98140fd..812cae6 100644 --- a/sdk/typescript/src/typed/global-node.ts +++ b/sdk/typescript/src/typed/global-node.ts @@ -1,8 +1,8 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** diff --git a/sdk/typescript/src/typed/index.ts b/sdk/typescript/src/typed/index.ts index 72450a8..d715891 100644 --- a/sdk/typescript/src/typed/index.ts +++ b/sdk/typescript/src/typed/index.ts @@ -9,6 +9,7 @@ export { type GlobalNode, globalNode } from "./global-node.js"; export { type Qa, qa } from "./qa.js"; export { type StartCall, startCall } from "./start-call.js"; export { type Trigger, trigger } from "./trigger.js"; +export { type Tuner, tuner } from "./tuner.js"; export { type Webhook, webhook } from "./webhook.js"; import type { @@ -18,8 +19,9 @@ import type { Qa, StartCall, Trigger, + Tuner, Webhook, } from "./index.js"; /** Discriminated union of every generated typed node. */ -export type TypedNode = AgentNode | EndCall | GlobalNode | Qa | StartCall | Trigger | Webhook; +export type TypedNode = AgentNode | EndCall | GlobalNode | Qa | StartCall | Trigger | Tuner | Webhook; diff --git a/sdk/typescript/src/typed/qa.ts b/sdk/typescript/src/typed/qa.ts index 7f93152..e7f5233 100644 --- a/sdk/typescript/src/typed/qa.ts +++ b/sdk/typescript/src/typed/qa.ts @@ -1,8 +1,8 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** diff --git a/sdk/typescript/src/typed/start-call.ts b/sdk/typescript/src/typed/start-call.ts index f243850..5ed9bb2 100644 --- a/sdk/typescript/src/typed/start-call.ts +++ b/sdk/typescript/src/typed/start-call.ts @@ -1,11 +1,11 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** - * Each entry declares one variable to capture, with its name, data type, and per-variable extraction hint. + * Each entry declares one variable to capture, with its name, data type, and extraction hint. */ export interface StartCallExtraction_variablesRow { /** @@ -68,7 +68,7 @@ export interface StartCall { */ delayed_start_duration?: number; /** - * When true, runs an LLM extraction pass on transition out of this node to capture variables from the opening turn. + * When true, runs an LLM extraction pass for this node. */ extraction_enabled?: boolean; /** @@ -76,7 +76,7 @@ export interface StartCall { */ extraction_prompt?: string; /** - * Each entry declares one variable to capture, with its name, data type, and per-variable extraction hint. + * Each entry declares one variable to capture, with its name, data type, and extraction hint. */ extraction_variables?: Array; /** diff --git a/sdk/typescript/src/typed/trigger.ts b/sdk/typescript/src/typed/trigger.ts index 2dbe27d..02a6257 100644 --- a/sdk/typescript/src/typed/trigger.ts +++ b/sdk/typescript/src/typed/trigger.ts @@ -1,8 +1,8 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** diff --git a/sdk/typescript/src/typed/tuner.ts b/sdk/typescript/src/typed/tuner.ts new file mode 100644 index 0000000..fc9170c --- /dev/null +++ b/sdk/typescript/src/typed/tuner.ts @@ -0,0 +1,40 @@ +// GENERATED — do not edit by hand. +// +// Regenerate with `npm run codegen` against the target Dograh backend. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. + + +/** + * Export the completed call to Tuner for Agent Observability + * + * LLM hint: Tuner is a post-call observability export. It does not participate in the conversation graph and should not be connected to other nodes. + */ +export interface Tuner { + type: "tuner"; + /** + * Short identifier for this Tuner export configuration. + */ + name?: string; + /** + * When false, Dograh skips exporting this call to Tuner. + */ + tuner_enabled?: boolean; + /** + * The agent identifier registered in your Tuner workspace. + */ + tuner_agent_id: string; + /** + * Your numeric Tuner workspace ID. + */ + tuner_workspace_id: number; + /** + * Bearer token used when posting completed calls to Tuner. + */ + tuner_api_key: string; +} + +/** Factory — sets `type` for you so you don't repeat the discriminator. */ +export function tuner(input: Omit): Tuner { + return { type: "tuner", ...input }; +} diff --git a/sdk/typescript/src/typed/webhook.ts b/sdk/typescript/src/typed/webhook.ts index f3d39f2..87cfea0 100644 --- a/sdk/typescript/src/typed/webhook.ts +++ b/sdk/typescript/src/typed/webhook.ts @@ -1,8 +1,8 @@ // GENERATED — do not edit by hand. // // Regenerate with `npm run codegen` against the target Dograh backend. -// Source of truth: each node's NodeSpec in the backend's -// `api/services/workflow/node_specs/` directory. +// Source of truth: the backend's model-backed node-spec catalog served +// from `/api/v1/node-types`. /** * Additional HTTP headers to include with the request. diff --git a/ui/package-lock.json b/ui/package-lock.json index db13e2f..01c5fad 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,15 +1,14 @@ { "name": "ui", - "version": "1.28.0", + "version": "1.30.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ui", - "version": "1.28.0", + "version": "1.30.1", "dependencies": { "@dagrejs/dagre": "^1.1.4", - "@nangohq/frontend": "^0.69.47", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.12", @@ -2485,26 +2484,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@nangohq/frontend": { - "version": "0.69.47", - "resolved": "https://registry.npmjs.org/@nangohq/frontend/-/frontend-0.69.47.tgz", - "integrity": "sha512-PyRSh6DTXkqya40v4EwJ8myiGV99H4izlnkD9Ldz/Aenb/Q7r6nrP41U7dHn8phdHCOEsLLF9y9CjKXBnA5Smg==", - "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", - "dependencies": { - "@nangohq/types": "0.69.47" - } - }, - "node_modules/@nangohq/types": { - "version": "0.69.47", - "resolved": "https://registry.npmjs.org/@nangohq/types/-/types-0.69.47.tgz", - "integrity": "sha512-mgw3hQbtSeYbxj/A6ncjg31oJiZkR7FoNHwbv7LpULBDg6C2J9gZ0UbFtZfS8XZxOXUoLWvRYKN0rHcVrD+MmA==", - "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", - "dependencies": { - "axios": "1.13.5", - "json-schema": "0.4.0", - "type-fest": "4.41.0" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz", diff --git a/ui/package.json b/ui/package.json index ed670c2..a2bda20 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,7 +13,6 @@ }, "dependencies": { "@dagrejs/dagre": "^1.1.4", - "@nangohq/frontend": "^0.69.47", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.12", diff --git a/ui/src/app/campaigns/GoogleSheetSelector.tsx b/ui/src/app/campaigns/GoogleSheetSelector.tsx deleted file mode 100644 index de3cfc6..0000000 --- a/ui/src/app/campaigns/GoogleSheetSelector.tsx +++ /dev/null @@ -1,237 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { toast } from 'sonner'; - -import { getIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGet, getIntegrationsApiV1IntegrationGet } from '@/client/sdk.gen'; -import type { IntegrationResponse } from '@/client/types.gen'; -import { Button } from '@/components/ui/button'; -import { Label } from '@/components/ui/label'; -import logger from '@/lib/logger'; - -interface GoogleSheetSelectorProps { - accessToken: string; - onSheetSelected: (sheetUrl: string, sheetName: string) => void; - selectedSheetUrl?: string; -} - -interface PickerBuilder { - addView: (viewId: string) => PickerBuilder; - setOAuthToken: (token: string) => PickerBuilder; - setDeveloperKey: (key: string) => PickerBuilder; - setCallback: (callback: (data: { action: string; docs?: Array<{ id: string; name: string; url: string }> }) => void) => PickerBuilder; - setTitle: (title: string) => PickerBuilder; - build: () => { setVisible: (visible: boolean) => void }; -} - -declare global { - interface Window { - gapi: { - load: (library: string, callback: () => void) => void; - }; - google: { - picker: { - PickerBuilder: new () => PickerBuilder; - ViewId: { - SPREADSHEETS: string; - }; - Action: { - PICKED: string; - }; - }; - }; - } -} - -// Google API configuration -const GOOGLE_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_API_KEY || ''; - -export default function GoogleSheetSelector({ accessToken, onSheetSelected, selectedSheetUrl }: GoogleSheetSelectorProps) { - const [loading, setLoading] = useState(false); - const [pickerApiLoaded, setPickerApiLoaded] = useState(false); - const [googleIntegration, setGoogleIntegration] = useState(null); - const [selectedSheetName, setSelectedSheetName] = useState(''); - const [checkingIntegration, setCheckingIntegration] = useState(true); - - // Load Google Picker API - useEffect(() => { - const script = document.createElement('script'); - script.src = 'https://apis.google.com/js/api.js'; - script.onload = () => { - window.gapi.load('picker', () => { - setPickerApiLoaded(true); - logger.info('Google Picker API loaded'); - }); - }; - document.body.appendChild(script); - - return () => { - if (document.body.contains(script)) { - document.body.removeChild(script); - } - }; - }, []); - - // Check for Google Sheet integration - useEffect(() => { - const checkGoogleIntegration = async () => { - if (!accessToken) { - return; - } - - try { - const response = await getIntegrationsApiV1IntegrationGet({ - headers: { - 'Authorization': `Bearer ${accessToken}`, - } - }); - - if (response.data) { - const integrations = Array.isArray(response.data) ? response.data : [response.data]; - const googleSheet = integrations.find((i: IntegrationResponse) => i.provider === 'google-sheet'); - setGoogleIntegration(googleSheet || null); - } - } catch (error) { - logger.error('Failed to check Google integration:', error); - } finally { - setCheckingIntegration(false); - } - }; - - checkGoogleIntegration(); - }, [accessToken]); - - const fetchGoogleAccessToken = async () => { - if (!googleIntegration) return null; - - try { - const response = await getIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGet({ - path: { - integration_id: googleIntegration.id, - }, - headers: { - Authorization: `Bearer ${accessToken}`, - } - }); - - if (response.data?.access_token) { - return response.data.access_token; - } - return null; - } catch (error) { - logger.error('Failed to fetch Google access token:', error); - return null; - } - }; - - const openGooglePicker = async () => { - if (!pickerApiLoaded) { - toast.error('Google Picker is still loading. Please try again.'); - return; - } - - if (!GOOGLE_API_KEY) { - toast.error('Google API Key is not configured.'); - return; - } - - if (!googleIntegration) { - toast.error('Please connect Google Sheets in the Integrations page first.'); - return; - } - - setLoading(true); - - try { - const token = await fetchGoogleAccessToken(); - if (!token) { - toast.error('Failed to get Google access token. Please re-authorize in Integrations.'); - setLoading(false); - return; - } - - const picker = new window.google.picker.PickerBuilder() - .addView(window.google.picker.ViewId.SPREADSHEETS) - .setOAuthToken(token) - .setDeveloperKey(GOOGLE_API_KEY) - .setCallback((data: { action: string; docs?: Array<{ id: string; name: string; url: string }> }) => { - if (data.action === window.google.picker.Action.PICKED && data.docs && data.docs.length > 0) { - const doc = data.docs[0]; - setSelectedSheetName(doc.name); - onSheetSelected(doc.url, doc.name); - toast.success(`Selected: ${doc.name}`); - } - setLoading(false); - }) - .setTitle('Select a Google Sheet for your campaign') - .build(); - - picker.setVisible(true); - } catch (error) { - toast.error('Error opening Google Picker'); - logger.error('Error opening Google Picker:', error); - setLoading(false); - } - }; - - if (checkingIntegration) { - return ( -
- -
Checking Google integration...
-
- ); - } - - if (!googleIntegration) { - return ( -
- -
-

- Google Sheets integration not found -

-

- Please go to the{' '} - - Integrations page - - {' '}and connect your Google account first. -

-
-
- ); - } - - return ( -
- -
- - {selectedSheetUrl && ( - - )} -
-

- Select a Google Sheet from your connected Google account -

-
- ); -} diff --git a/ui/src/app/campaigns/new/page.tsx b/ui/src/app/campaigns/new/page.tsx index 4d77841..aab2adc 100644 --- a/ui/src/app/campaigns/new/page.tsx +++ b/ui/src/app/campaigns/new/page.tsx @@ -30,7 +30,6 @@ import { useAuth } from '@/lib/auth'; import CampaignAdvancedSettings, { getTimezoneValue, type TimeSlot } from '../CampaignAdvancedSettings'; import CsvUploadSelector from '../CsvUploadSelector'; -import GoogleSheetSelector from '../GoogleSheetSelector'; export default function NewCampaignPage() { const { user, getAccessToken, redirectToLogin, loading } = useAuth(); @@ -39,12 +38,11 @@ export default function NewCampaignPage() { // Form state const [campaignName, setCampaignName] = useState(''); const [selectedWorkflowId, setSelectedWorkflowId] = useState(''); - const [sourceType, setSourceType] = useState<'google-sheet' | 'csv'>('csv'); + const [sourceType, setSourceType] = useState<'csv'>('csv'); const [sourceId, setSourceId] = useState(''); const [selectedFileName, setSelectedFileName] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [createError, setCreateError] = useState(null); - const [userAccessToken, setUserAccessToken] = useState(''); // Workflows state const [workflows, setWorkflows] = useState([]); @@ -97,7 +95,6 @@ export default function NewCampaignPage() { if (!user) return; try { const accessToken = await getAccessToken(); - setUserAccessToken(accessToken); const response = await getWorkflowsSummaryApiV1WorkflowSummaryGet({ headers: { 'Authorization': `Bearer ${accessToken}`, @@ -342,12 +339,6 @@ export default function NewCampaignPage() { router.push('/campaigns'); }; - // Handle sheet selection - const handleSheetSelected = (sheetUrl: string) => { - setSourceId(sheetUrl); - setCreateError(null); - }; - // Handle CSV file upload const handleFileUploaded = (fileKey: string, fileName: string) => { setSourceId(fileKey); @@ -481,7 +472,7 @@ export default function NewCampaignPage() { @@ -500,18 +490,10 @@ export default function NewCampaignPage() {

- {sourceType === 'google-sheet' ? ( - - ) : ( - - )} + {/* Advanced Settings */} { - setIsLoading(true); - try { - if (!user) { - throw new Error('User not authenticated'); - } - const accessToken = await getAccessToken(); - - // Fetch session details from our API - const sessionResponse = await createSessionApiV1IntegrationSessionPost({ - headers: { - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (!sessionResponse.data?.session_token) { - throw new Error('Failed to get session token'); - } - - // Initialize Nango and open connect UI - const nango = new Nango(); - const connect = nango.openConnectUI({ - onEvent: (event) => { - if (event.type === 'close') { - // Handle modal closed - setIsLoading(false); - logger.info('Nango connect UI closed'); - } else if (event.type === 'connect') { - // Handle auth flow successful - setIsLoading(false); - logger.info('Integration connected successfully'); - // Refresh the page to show new integrations - window.location.reload(); - } - }, - }); - - // Set the session token to initialize the connect UI - connect.setSessionToken(sessionResponse.data.session_token); - - } catch (err) { - logger.error(`Error creating integration: ${err}`); - setIsLoading(false); - // You might want to show a toast notification here - alert('Failed to create integration. Please try again.'); - } - }; - - return ( - - ); -} diff --git a/ui/src/app/integrations/[id]/gmail/page.tsx b/ui/src/app/integrations/[id]/gmail/page.tsx deleted file mode 100644 index 7bb1695..0000000 --- a/ui/src/app/integrations/[id]/gmail/page.tsx +++ /dev/null @@ -1,390 +0,0 @@ -'use client'; - -import { useParams, useRouter } from 'next/navigation'; -import { useCallback, useEffect, useState } from 'react'; - -import { getIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGet } from '@/client/sdk.gen'; -import { useAuth } from '@/lib/auth'; -import logger from '@/lib/logger'; - -interface Email { - id: string; - threadId: string; - subject: string; - from: string; - snippet: string; - date: string; -} - -interface EmailDetail { - id: string; - threadId: string; - subject: string; - from: string; - to: string; - date: string; - body: string; -} - -interface GmailHeader { - name: string; - value: string; -} - -interface GmailPayloadPart { - mimeType: string; - body: { - data?: string; - }; -} - -export default function GmailSearchPage() { - const params = useParams(); - const router = useRouter(); - const integrationId = parseInt(params.id as string); - const { getAccessToken, redirectToLogin } = useAuth(); - - const [accessToken, setAccessToken] = useState(null); - const [searchQuery, setSearchQuery] = useState(''); - const [emails, setEmails] = useState([]); - const [selectedEmail, setSelectedEmail] = useState(null); - const [replyText, setReplyText] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [sendingReply, setSendingReply] = useState(false); - - const fetchAccessToken = useCallback(async () => { - try { - const token = await getAccessToken(); - if (!token) { - redirectToLogin(); - return; - } - - const response = await getIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGet({ - path: { integration_id: integrationId }, - headers: { Authorization: `Bearer ${token}` }, - }); - - if (response.data?.access_token) { - setAccessToken(response.data.access_token); - } else { - setError('Failed to get access token'); - } - } catch (err) { - logger.error('Error fetching access token:', err); - setError('Failed to fetch access token. Please try again.'); - } - }, [getAccessToken, redirectToLogin, integrationId]); - - useEffect(() => { - fetchAccessToken(); - }, [fetchAccessToken]); - - const searchEmails = async () => { - if (!accessToken || !searchQuery.trim()) return; - - setLoading(true); - setError(null); - setEmails([]); - setSelectedEmail(null); - - try { - const response = await fetch( - `https://gmail.googleapis.com/gmail/v1/users/me/messages?q=${encodeURIComponent(searchQuery)}&maxResults=20`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - } - ); - - if (!response.ok) { - throw new Error(`Gmail API error: ${response.statusText}`); - } - - const data = await response.json(); - - if (!data.messages || data.messages.length === 0) { - setEmails([]); - return; - } - - // Fetch details for each message - const emailPromises = data.messages.map(async (msg: { id: string }) => { - const msgResponse = await fetch( - `https://gmail.googleapis.com/gmail/v1/users/me/messages/${msg.id}?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=Date`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - } - ); - return msgResponse.json(); - }); - - const emailDetails = await Promise.all(emailPromises); - - const formattedEmails: Email[] = emailDetails.map((email) => { - const headers = email.payload.headers as GmailHeader[]; - const subject = headers.find((h) => h.name === 'Subject')?.value || 'No Subject'; - const from = headers.find((h) => h.name === 'From')?.value || 'Unknown'; - const date = headers.find((h) => h.name === 'Date')?.value || ''; - - return { - id: email.id, - threadId: email.threadId, - subject, - from, - snippet: email.snippet || '', - date, - }; - }); - - setEmails(formattedEmails); - } catch (err) { - logger.error('Error searching emails:', err); - setError('Failed to search emails. Please try again.'); - } finally { - setLoading(false); - } - }; - - const loadEmailDetail = async (emailId: string) => { - if (!accessToken) return; - - setLoading(true); - setError(null); - - try { - const response = await fetch( - `https://gmail.googleapis.com/gmail/v1/users/me/messages/${emailId}?format=full`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - } - ); - - if (!response.ok) { - throw new Error(`Gmail API error: ${response.statusText}`); - } - - const email = await response.json(); - const headers = email.payload.headers as GmailHeader[]; - - const subject = headers.find((h) => h.name === 'Subject')?.value || 'No Subject'; - const from = headers.find((h) => h.name === 'From')?.value || 'Unknown'; - const to = headers.find((h) => h.name === 'To')?.value || 'Unknown'; - const date = headers.find((h) => h.name === 'Date')?.value || ''; - - // Extract email body - let body = ''; - if (email.payload.body.data) { - body = atob(email.payload.body.data.replace(/-/g, '+').replace(/_/g, '/')); - } else if (email.payload.parts) { - const parts = email.payload.parts as GmailPayloadPart[]; - const textPart = parts.find((part) => part.mimeType === 'text/plain'); - if (textPart && textPart.body.data) { - body = atob(textPart.body.data.replace(/-/g, '+').replace(/_/g, '/')); - } - } - - setSelectedEmail({ - id: email.id, - threadId: email.threadId, - subject, - from, - to, - date, - body, - }); - } catch (err) { - logger.error('Error loading email detail:', err); - setError('Failed to load email details. Please try again.'); - } finally { - setLoading(false); - } - }; - - const sendReply = async () => { - if (!accessToken || !selectedEmail || !replyText.trim()) return; - - setSendingReply(true); - setError(null); - - try { - // Create the email message - const to = selectedEmail.from.match(/<(.+)>/)?.[1] || selectedEmail.from; - const subject = selectedEmail.subject.startsWith('Re:') - ? selectedEmail.subject - : `Re: ${selectedEmail.subject}`; - - const messageParts = [ - `To: ${to}`, - `Subject: ${subject}`, - `In-Reply-To: ${selectedEmail.id}`, - `References: ${selectedEmail.id}`, - '', - replyText, - ]; - - const message = messageParts.join('\n'); - const encodedMessage = btoa(message) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=+$/, ''); - - const response = await fetch( - `https://gmail.googleapis.com/gmail/v1/users/me/messages/send`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - raw: encodedMessage, - threadId: selectedEmail.threadId, - }), - } - ); - - if (!response.ok) { - throw new Error(`Gmail API error: ${response.statusText}`); - } - - alert('Reply sent successfully!'); - setReplyText(''); - } catch (err) { - logger.error('Error sending reply:', err); - setError('Failed to send reply. Please try again.'); - } finally { - setSendingReply(false); - } - }; - - return ( -
-
- -

Gmail Search

-
- - {/* Search Section */} -
-
- setSearchQuery(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && searchEmails()} - placeholder="Search emails (e.g., from:user@example.com, subject:meeting)" - className="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - disabled={!accessToken || loading} - /> - -
-
- - {error && ( -
- {error} -
- )} - -
- {/* Email List */} -
-
-

Search Results

-
-
- {emails.length === 0 && !loading && ( -
- {searchQuery ? 'No emails found' : 'Enter a search query to find emails'} -
- )} - {emails.map((email) => ( -
loadEmailDetail(email.id)} - className={`p-4 cursor-pointer hover:bg-muted/50 ${ - selectedEmail?.id === email.id ? 'bg-accent' : '' - }`} - > -
{email.subject}
-
{email.from}
-
{email.snippet}
-
{email.date}
-
- ))} -
-
- - {/* Email Detail and Reply */} -
-
-

Email Details

-
- {selectedEmail ? ( -
-
-
- Subject: {selectedEmail.subject} -
-
- From: {selectedEmail.from} -
-
- To: {selectedEmail.to} -
-
- Date: {selectedEmail.date} -
-
- -
-
{selectedEmail.body}
-
- -
-

Reply

-