From 5f28c1b2a9b17ed19f8a2b4118d1d4eb8c4249a7 Mon Sep 17 00:00:00 2001
From: Mohamed-Mamdouh
Date: Wed, 20 May 2026 10:07:33 +0100
Subject: [PATCH] feat: add Tuner Integration to Dograh (#311)
* Add tuner integration
* bump pipecat version
* chore: update pipecat submodule to match upstream and use tuner-pipecat-sdk 0.2.0
Update pipecat submodule from 0.0.109.dev23 to 13e98d0d9 (the exact commit
upstream dograh-hq/dograh uses after v1.30.1). This installs pipecat-ai as
1.1.0.post277 via setuptools_scm, satisfying tuner-pipecat-sdk 0.2.0's
pipecat-ai>=1.0.0 requirement.
Co-Authored-By: Claude Sonnet 4.6
* wire tuner
* feat: refactor integrations into self contained packages
* chore: simplify ensure_public_access_token
* fix: remove NodeSpec and make DTOs the source of truth
* feat: send relevant signal to mcp using to_mcp_dict
* fix: fix tests
* cleanup: remove nango integrations
* feat: add agents.md for integrations
---------
Co-authored-by: Claude Sonnet 4.6
Co-authored-by: Abhishek Kumar
---
api/constants.py | 2 +
api/db/models.py | 8 +-
api/mcp_server/tools/node_types.py | 14 +-
api/requirements.txt | 1 +
api/routes/campaign.py | 6 +-
api/routes/integration.py | 266 -----
api/routes/main.py | 6 +-
api/routes/public_download.py | 5 +-
api/services/campaign/source_sync.py | 4 +-
api/services/campaign/source_sync_factory.py | 3 -
api/services/campaign/sources/__init__.py | 4 +-
.../campaign/sources/google_sheets.py | 224 -----
api/services/configuration/masking.py | 58 +-
api/services/integrations/AGENTS.md | 239 +++++
api/services/integrations/__init__.py | 39 +
api/services/integrations/base.py | 69 ++
api/services/integrations/loader.py | 21 +
api/services/integrations/nango.py | 253 -----
api/services/integrations/registry.py | 128 +++
api/services/integrations/tuner/__init__.py | 19 +
api/services/integrations/tuner/client.py | 71 ++
api/services/integrations/tuner/collector.py | 182 ++++
api/services/integrations/tuner/completion.py | 76 ++
api/services/integrations/tuner/node.py | 139 +++
api/services/integrations/tuner/runtime.py | 101 ++
api/services/pipecat/event_handlers.py | 34 +-
api/services/pipecat/run_pipeline.py | 25 +
api/services/workflow/audit.py | 8 +-
api/services/workflow/dto.py | 932 ++++++++++++++++--
api/services/workflow/node_data.py | 19 +
api/services/workflow/node_specs/__init__.py | 51 +-
api/services/workflow/node_specs/_base.py | 85 +-
api/services/workflow/node_specs/agent.py | 168 ----
api/services/workflow/node_specs/constants.py | 44 +
api/services/workflow/node_specs/end_call.py | 141 ---
.../workflow/node_specs/global_node.py | 77 --
.../workflow/node_specs/model_spec.py | 404 ++++++++
api/services/workflow/node_specs/qa.py | 203 ----
.../workflow/node_specs/start_call.py | 250 -----
api/services/workflow/node_specs/trigger.py | 79 --
api/services/workflow/node_specs/webhook.py | 133 ---
api/services/workflow/pipecat_engine.py | 23 +-
api/services/workflow/workflow_graph.py | 23 +-
api/tasks/campaign_tasks.py | 2 +-
api/tasks/run_integrations.py | 46 +-
api/tests/conftest.py | 37 +-
.../sample_branching_workflow.json | 4 +-
api/tests/test_dograh_sdk_typed.py | 11 +-
api/tests/test_dto.py | 31 +
api/tests/test_node_specs.py | 237 ++++-
api/tests/test_pipecat_engine_end_call.py | 9 +-
api/tests/test_text_and_audio_playback.py | 27 +-
api/tests/test_workflow_qa_masking.py | 42 +
docs/api-reference/openapi.json | 2 +-
scripts/generate_sdk.sh | 8 +-
scripts/setup_pipecat.sh | 25 +
.../src/dograh_sdk/_generated_models.py | 4 +-
sdk/python/src/dograh_sdk/codegen.py | 4 +-
sdk/python/src/dograh_sdk/typed/__init__.py | 2 +
sdk/python/src/dograh_sdk/typed/agent_node.py | 15 +-
sdk/python/src/dograh_sdk/typed/end_call.py | 8 +-
.../src/dograh_sdk/typed/global_node.py | 4 +-
sdk/python/src/dograh_sdk/typed/qa.py | 4 +-
sdk/python/src/dograh_sdk/typed/start_call.py | 11 +-
sdk/python/src/dograh_sdk/typed/trigger.py | 4 +-
sdk/python/src/dograh_sdk/typed/tuner.py | 50 +
sdk/python/src/dograh_sdk/typed/webhook.py | 4 +-
sdk/typescript/scripts/codegen.mts | 4 +-
sdk/typescript/src/typed/agent-node.ts | 10 +-
sdk/typescript/src/typed/end-call.ts | 8 +-
sdk/typescript/src/typed/global-node.ts | 4 +-
sdk/typescript/src/typed/index.ts | 4 +-
sdk/typescript/src/typed/qa.ts | 4 +-
sdk/typescript/src/typed/start-call.ts | 10 +-
sdk/typescript/src/typed/trigger.ts | 4 +-
sdk/typescript/src/typed/tuner.ts | 40 +
sdk/typescript/src/typed/webhook.ts | 4 +-
ui/package-lock.json | 25 +-
ui/package.json | 1 -
ui/src/app/campaigns/GoogleSheetSelector.tsx | 237 -----
ui/src/app/campaigns/new/page.tsx | 30 +-
.../integrations/CreateIntegrationButton.tsx | 69 --
ui/src/app/integrations/[id]/gmail/page.tsx | 390 --------
ui/src/app/integrations/page.tsx | 174 ----
.../workflow/[workflowId]/RenderWorkflow.tsx | 26 +-
.../[workflowId]/utils/layoutNodes.ts | 56 +-
ui/src/client/index.ts | 4 +-
ui/src/client/sdk.gen.ts | 60 +-
ui/src/client/types.gen.ts | 260 -----
ui/src/components/flow/nodes/GenericNode.tsx | 100 +-
.../flow/nodes/common/NodeContent.tsx | 38 +-
ui/src/components/flow/types.ts | 9 +-
.../workflow/CreateWorkflowButton.tsx | 3 -
93 files changed, 3388 insertions(+), 3414 deletions(-)
delete mode 100644 api/routes/integration.py
delete mode 100644 api/services/campaign/sources/google_sheets.py
create mode 100644 api/services/integrations/AGENTS.md
create mode 100644 api/services/integrations/base.py
create mode 100644 api/services/integrations/loader.py
delete mode 100644 api/services/integrations/nango.py
create mode 100644 api/services/integrations/registry.py
create mode 100644 api/services/integrations/tuner/__init__.py
create mode 100644 api/services/integrations/tuner/client.py
create mode 100644 api/services/integrations/tuner/collector.py
create mode 100644 api/services/integrations/tuner/completion.py
create mode 100644 api/services/integrations/tuner/node.py
create mode 100644 api/services/integrations/tuner/runtime.py
create mode 100644 api/services/workflow/node_data.py
delete mode 100644 api/services/workflow/node_specs/agent.py
create mode 100644 api/services/workflow/node_specs/constants.py
delete mode 100644 api/services/workflow/node_specs/end_call.py
delete mode 100644 api/services/workflow/node_specs/global_node.py
create mode 100644 api/services/workflow/node_specs/model_spec.py
delete mode 100644 api/services/workflow/node_specs/qa.py
delete mode 100644 api/services/workflow/node_specs/start_call.py
delete mode 100644 api/services/workflow/node_specs/trigger.py
delete mode 100644 api/services/workflow/node_specs/webhook.py
create mode 100755 scripts/setup_pipecat.sh
create mode 100644 sdk/python/src/dograh_sdk/typed/tuner.py
create mode 100644 sdk/typescript/src/typed/tuner.ts
delete mode 100644 ui/src/app/campaigns/GoogleSheetSelector.tsx
delete mode 100644 ui/src/app/integrations/CreateIntegrationButton.tsx
delete mode 100644 ui/src/app/integrations/[id]/gmail/page.tsx
delete mode 100644 ui/src/app/integrations/page.tsx
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 (
-