diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9e6adb29..b24a4409 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -49,7 +49,7 @@ RUN --mount=type=bind,source=api/requirements.txt,target=/tmp/req.txt \ # sys.prefix/nltk_data, so it travels with the venv on COPY/rsync. RUN --mount=type=bind,source=pipecat,target=/tmp/pipecat,rw \ --mount=type=cache,target=/root/.cache/uv \ - uv pip install '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp]' \ + uv pip install '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp,inworld,smallest]' \ && uv pip install --group /tmp/pipecat/pyproject.toml:dev \ && uv pip uninstall opencv-python \ && uv pip install opencv-python-headless \ diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 2d129552..6c565564 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -29,7 +29,9 @@ services: - dograh-venv:/workspaces/dograh/venv - dograh-ui-node_modules:/workspaces/dograh/ui/node_modules - dograh-ts-validator-node_modules:/workspaces/dograh/api/mcp_server/ts_validator/node_modules - + ports: + - "127.0.0.1:3000:3000" + - "127.0.0.1:8000:8000" volumes: dograh-venv: dograh-ui-node_modules: diff --git a/.gitignore b/.gitignore index 4fa3938e..a2250980 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ node_modules/ # Local self-signed TLS for the remote stack (regenerate with ./generate_certificate.sh). # NEVER commit the private key. certs/ +# Superpowers brainstorm mockups (local only) +.superpowers/ +docs/superpowers/ +.gstack/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8e1679a5..8dc5f12d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.35.0" + ".": "1.37.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 947586de..b6969f19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## 1.37.0 (2026-06-19) + + + +## What's Changed +### Features +* feat: add Inworld TTS provider support by @manasseh-zw in https://github.com/dograh-hq/dograh/pull/420 +### Bug Fixes +* fix(workflow): detect duplicate trigger paths when first node has no id by @Mubashirrrr in https://github.com/dograh-hq/dograh/pull/409 +* fix(qa): tolerate non-dict JSON from QA LLM instead of crashing by @Mubashirrrr in https://github.com/dograh-hq/dograh/pull/408 +* fix(devcontainer): expose UI/API ports for host access by @faisu in https://github.com/dograh-hq/dograh/pull/405 +* fix: disable duplicate trigger nodes in workflow builder by @nuthalapativarun in https://github.com/dograh-hq/dograh/pull/402 +* fix(ui): proxy WebSocket signaling upgrade so local web calls work (#425) by @yogi6969 in https://github.com/dograh-hq/dograh/pull/454 + +## New Contributors +* @faisu made their first contribution in https://github.com/dograh-hq/dograh/pull/405 +* @yogi6969 made their first contribution in https://github.com/dograh-hq/dograh/pull/454 + +**Full Changelog**: https://github.com/dograh-hq/dograh/compare/dograh-v1.36.0...dograh-v1.37.0 + +## 1.36.0 (2026-06-18) + + + +## What's Changed +### Features +* feat: add Smallest AI TTS and STT provider integration by @harshitajain165 in https://github.com/dograh-hq/dograh/pull/444 +* feat: refreshed user onboarding by @a6kme in https://github.com/dograh-hq/dograh/pull/430 +* feat: add custom sarvam tts voice by @chewwbaka in https://github.com/dograh-hq/dograh/pull/449 +* feat(examples): add load-and-edit workflow SDK example in Python and TypeScript by @nuthalapativarun in https://github.com/dograh-hq/dograh/pull/441 +* feat(examples): add multi-node Workflow SDK example in Python and TypeScript by @nuthalapativarun in https://github.com/dograh-hq/dograh/pull/440 +### Bug Fixes +* fix: add pace option in sarvam tts config by @chewwbaka in https://github.com/dograh-hq/dograh/pull/447 +* fix(ui): release microphone stream on call teardown so a second test call works by @Aymenbenpakiss in https://github.com/dograh-hq/dograh/pull/446 +* fix: add language field to CartesiaTTSConfiguration and pass to Cartesia TTS service by @nuthalapativarun in https://github.com/dograh-hq/dograh/pull/442 +* fix: sync Smallest AI voice dropdown with selected model by @harshitajain165 in https://github.com/dograh-hq/dograh/pull/451 +### Other Changes +* Validate workflow status filter to prevent 500 on invalid enum value by @a6kme in https://github.com/dograh-hq/dograh/pull/450 +* allow self-hosters to enable Stack Auth via Dockerfile build args (v33.0) by @neggmmm in https://github.com/dograh-hq/dograh/pull/445 + +## New Contributors +* @harshitajain165 made their first contribution in https://github.com/dograh-hq/dograh/pull/444 +* @Aymenbenpakiss made their first contribution in https://github.com/dograh-hq/dograh/pull/446 +* @neggmmm made their first contribution in https://github.com/dograh-hq/dograh/pull/445 + +**Full Changelog**: https://github.com/dograh-hq/dograh/compare/dograh-v1.35.0...dograh-v1.36.0 + ## 1.35.0 (2026-06-12) diff --git a/README.md b/README.md index be5f83b1..549f814a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,17 @@ An honest comparison on the axes that matter most to teams evaluating voice AI p curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml && curl -o start_docker.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.sh && chmod +x start_docker.sh && ./start_docker.sh ``` +> **⚡ Prefer an AI agent to set it up for you?** +> If you use **Claude Code** or **Codex**, install the official [Dograh setup skill](https://github.com/dograh-hq/dograh-plugins) and let your agent handle installation, configuration, and troubleshooting — it detects your OS, picks the right deploy path, runs Dograh's own setup scripts, and verifies the result. +> +> ```text +> # In Claude Code +> /plugin marketplace add dograh-hq/dograh-plugins +> /plugin install dograh@dograh +> ``` +> +> Then start a new session and ask it to _"set up Dograh"_ (or run `/dograh-setup`). Codex is supported too — see the [plugin repo](https://github.com/dograh-hq/dograh-plugins#install). + > **Note** > First startup may take 2-3 minutes to download all images. Once running, open http://localhost:3010 to create your first AI voice assistant! > For common issues and solutions, see 🔧 **[Troubleshooting](docs/troubleshooting.md)**. diff --git a/README.zh-CN.md b/README.zh-CN.md index 3b2af0d3..a6cf3a07 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -84,6 +84,17 @@ curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml && REGISTRY=ghcr.io/dograh-hq ENABLE_TELEMETRY=true docker compose up --pull always ``` +> **⚡ 想让 AI 智能体帮你完成部署?** +> 如果你使用 **Claude Code** 或 **Codex**,可以安装官方的 [Dograh 部署技能(skill)](https://github.com/dograh-hq/dograh-plugins),让智能体替你完成安装、配置与排障——它会识别你的操作系统、选择合适的部署方式、运行 Dograh 自带的部署脚本并验证结果。 +> +> ```text +> # 在 Claude Code 中 +> /plugin marketplace add dograh-hq/dograh-plugins +> /plugin install dograh@dograh +> ``` +> +> 然后开启一个新会话,让它 _"set up Dograh"_(或运行 `/dograh-setup`)。Codex 同样支持——详见[插件仓库](https://github.com/dograh-hq/dograh-plugins#install)。 + > **提示** > 首次启动需要 2-3 分钟拉取所有镜像。启动完成后,打开 http://localhost:3010 即可创建你的第一个 AI 语音助手! > 常见问题及解决方案请参见 🔧 **[故障排查](docs/troubleshooting.md)**。 diff --git a/api/Dockerfile b/api/Dockerfile index e1a7463b..621d1530 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -37,7 +37,7 @@ RUN --mount=type=bind,source=api/requirements.txt,target=/tmp/req.txt \ # sys.prefix/nltk_data, so it travels with the venv on COPY. RUN --mount=type=bind,source=pipecat,target=/tmp/pipecat,rw \ --mount=type=cache,target=/root/.cache/uv \ - uv pip install '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp]' \ + uv pip install '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp,inworld,smallest]' \ && uv pip uninstall opencv-python \ && uv pip install opencv-python-headless \ && python -c "import nltk; nltk.download('punkt_tab', download_dir='/opt/venv/nltk_data', quiet=True)" diff --git a/api/alembic/versions/91cc6ba3e1c7_add_key_to_user_configurations.py b/api/alembic/versions/91cc6ba3e1c7_add_key_to_user_configurations.py new file mode 100644 index 00000000..6bdc8b4e --- /dev/null +++ b/api/alembic/versions/91cc6ba3e1c7_add_key_to_user_configurations.py @@ -0,0 +1,52 @@ +"""add key to user_configurations + +Turns user_configurations into a per-user keyed JSON store mirroring +organization_configurations. Existing rows (the legacy v1 AI model +configuration blob) are backfilled with key MODEL_CONFIGURATION. + +Revision ID: 91cc6ba3e1c7 +Revises: efe356f488f9 +Create Date: 2026-06-12 21:04:25.561529 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "91cc6ba3e1c7" +down_revision: Union[str, None] = "efe356f488f9" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Backfill existing rows (all legacy model-config blobs) via the server + # default, then drop the default — application code always supplies key. + op.add_column( + "user_configurations", + sa.Column( + "key", + sa.String(), + nullable=False, + server_default="MODEL_CONFIGURATION", + ), + ) + + op.create_unique_constraint( + "_user_configuration_key_uc", "user_configurations", ["user_id", "key"] + ) + op.alter_column("user_configurations", "key", server_default=None) + + +def downgrade() -> None: + op.drop_constraint( + "_user_configuration_key_uc", "user_configurations", type_="unique" + ) + # Non-model-config rows (e.g. ONBOARDING) have no meaning in the old + # single-blob schema; the old code would read them as the user's model + # config, so they must not survive the downgrade. + op.execute("DELETE FROM user_configurations WHERE key != 'MODEL_CONFIGURATION'") + op.drop_column("user_configurations", "key") diff --git a/api/alembic/versions/efe356f488f9_add_extra_column_in_workflow_runs.py b/api/alembic/versions/efe356f488f9_add_extra_column_in_workflow_runs.py new file mode 100644 index 00000000..fd64cfdb --- /dev/null +++ b/api/alembic/versions/efe356f488f9_add_extra_column_in_workflow_runs.py @@ -0,0 +1,34 @@ +"""add extra column in workflow runs + +Revision ID: efe356f488f9 +Revises: 384be6596b36 +Create Date: 2026-06-16 12:24:30.081058 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "efe356f488f9" +down_revision: Union[str, None] = "384be6596b36" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "workflow_runs", + sa.Column( + "extra", + sa.JSON(), + server_default=sa.text("'{}'::json"), + nullable=False, + ), + ) + + +def downgrade() -> None: + op.drop_column("workflow_runs", "extra") diff --git a/api/constants.py b/api/constants.py index e5d9d7f5..b7bc9f74 100644 --- a/api/constants.py +++ b/api/constants.py @@ -30,6 +30,12 @@ CORS_ALLOWED_ORIGINS = [ o.strip() for o in os.getenv("CORS_ALLOWED_ORIGINS", "").split(",") if o.strip() ] AUTH_PROVIDER = os.getenv("AUTH_PROVIDER", "local") +# Stack Auth public client config. These are safe to expose to the browser (the +# publishable client key is public by design, and the project id is non-sensitive), +# and are served to the UI at runtime via /api/v1/health so the frontend no longer +# needs them baked into the bundle at build time. +STACK_AUTH_PROJECT_ID = os.getenv("STACK_AUTH_PROJECT_ID") +STACK_PUBLISHABLE_CLIENT_KEY = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY") DOGRAH_MPS_SECRET_KEY = os.getenv("DOGRAH_MPS_SECRET_KEY", None) MPS_API_URL = os.getenv("MPS_API_URL", "https://services.dograh.com") diff --git a/api/db/campaign_client.py b/api/db/campaign_client.py index 7729a2ea..2c988afd 100644 --- a/api/db/campaign_client.py +++ b/api/db/campaign_client.py @@ -10,6 +10,7 @@ from api.db.filters import apply_workflow_run_filters, get_workflow_run_order_cl from api.db.models import CampaignModel, QueuedRunModel, WorkflowRunModel from api.schemas.workflow import WorkflowRunResponseSchema from api.services.workflow.run_usage_response import format_public_cost_info +from api.utils.recording_artifacts import get_recording_storage_key class CampaignClient(BaseDBClient): @@ -45,9 +46,11 @@ class CampaignClient(BaseDBClient): source_id=source_id, created_by=user_id, organization_id=organization_id, - retry_config=retry_config - if retry_config - else CampaignModel.retry_config.default.arg, + retry_config=( + retry_config + if retry_config + else CampaignModel.retry_config.default.arg + ), orchestrator_metadata=orchestrator_metadata, telephony_configuration_id=telephony_configuration_id, ) @@ -216,6 +219,12 @@ class CampaignClient(BaseDBClient): "is_completed": run.is_completed, "recording_url": run.recording_url, "transcript_url": run.transcript_url, + "user_recording_url": get_recording_storage_key( + run.extra, "user" + ), + "bot_recording_url": get_recording_storage_key( + run.extra, "bot" + ), "cost_info": format_public_cost_info( run.cost_info, run.usage_info ), @@ -270,9 +279,11 @@ class CampaignClient(BaseDBClient): source_id=parent_campaign.source_id, created_by=parent_campaign.created_by, organization_id=parent_campaign.organization_id, - retry_config=retry_config - if retry_config - else CampaignModel.retry_config.default.arg, + retry_config=( + retry_config + if retry_config + else CampaignModel.retry_config.default.arg + ), orchestrator_metadata=child_meta, rate_limit_per_second=parent_campaign.rate_limit_per_second, total_rows=len(queued_runs_data), @@ -338,8 +349,7 @@ class CampaignClient(BaseDBClient): # Retries create new queued_runs with suffixed source_uuids linked via # parent_queued_run_id, so group by the ROOT queued_run using a # recursive walk and pick the latest workflow_run across the tree. - sql = text( - f""" + sql = text(f""" WITH RECURSIVE run_tree AS ( SELECT id AS root_id, id AS run_id FROM queued_runs @@ -366,8 +376,7 @@ class CampaignClient(BaseDBClient): JOIN latest_run_per_root lr ON lr.root_id = q0.id WHERE q0.campaign_id = :cid AND ({tag_filter}) - """ - ) + """) async with self.async_session() as session: result = await session.execute(sql, {"cid": campaign_id}) diff --git a/api/db/models.py b/api/db/models.py index 696cb6e6..d2cfc42f 100644 --- a/api/db/models.py +++ b/api/db/models.py @@ -82,12 +82,24 @@ class UserModel(Base): class UserConfigurationModel(Base): + """Per-user keyed JSON store, mirroring organization_configurations. + + Keys are defined in UserConfigurationKey. The legacy v1 AI model + configuration lives under MODEL_CONFIGURATION; last_validated_at is only + meaningful for that key. + """ + __tablename__ = "user_configurations" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=True) + key = Column(String, nullable=False) configuration = Column(JSON, nullable=False, default=dict) last_validated_at = Column(DateTime(timezone=True), nullable=True) + __table_args__ = ( + UniqueConstraint("user_id", "key", name="_user_configuration_key_uc"), + ) + # New Organization model class OrganizationModel(Base): @@ -532,6 +544,9 @@ class WorkflowRunModel(Base): is_completed = Column(Boolean, default=False) recording_url = Column(String, nullable=True) transcript_url = Column(String, nullable=True) + extra = Column( + JSON, nullable=False, default=dict, server_default=text("'{}'::json") + ) # Store storage backend as string enum (s3, minio) storage_backend = Column( Enum("s3", "minio", name="storage_backend"), diff --git a/api/db/organization_usage_client.py b/api/db/organization_usage_client.py index dfca0538..d1a52e7c 100644 --- a/api/db/organization_usage_client.py +++ b/api/db/organization_usage_client.py @@ -18,8 +18,9 @@ from api.db.models import ( WorkflowModel, WorkflowRunModel, ) -from api.enums import OrganizationConfigurationKey +from api.enums import OrganizationConfigurationKey, UserConfigurationKey from api.schemas.ai_model_configuration import EffectiveAIModelConfiguration +from api.utils.recording_artifacts import get_recording_storage_key class OrganizationUsageClient(BaseDBClient): @@ -226,6 +227,9 @@ class OrganizationUsageClient(BaseDBClient): "call_duration_seconds": int(round(call_duration)), "recording_url": run.recording_url, "transcript_url": run.transcript_url, + "user_recording_url": get_recording_storage_key(run.extra, "user"), + "bot_recording_url": get_recording_storage_key(run.extra, "bot"), + "extra": run.extra, "public_access_token": run.public_access_token, "phone_number": phone_number, "caller_number": caller_number, @@ -343,7 +347,9 @@ class OrganizationUsageClient(BaseDBClient): if user_id: config_result = await session.execute( select(UserConfigurationModel).where( - UserConfigurationModel.user_id == user_id + UserConfigurationModel.user_id == user_id, + UserConfigurationModel.key + == UserConfigurationKey.MODEL_CONFIGURATION.value, ) ) config_obj = config_result.scalar_one_or_none() diff --git a/api/db/user_client.py b/api/db/user_client.py index 4ea0bca9..27cf7749 100644 --- a/api/db/user_client.py +++ b/api/db/user_client.py @@ -4,10 +4,12 @@ from datetime import datetime, timezone from loguru import logger from pydantic import ValidationError from sqlalchemy import func +from sqlalchemy.dialects.postgresql import insert from sqlalchemy.future import select from api.db.base_client import BaseDBClient from api.db.models import UserConfigurationModel, UserModel +from api.enums import UserConfigurationKey from api.schemas.ai_model_configuration import EffectiveAIModelConfiguration @@ -28,8 +30,6 @@ class UserClient(BaseDBClient): # Use PostgreSQL's INSERT ... ON CONFLICT DO NOTHING # This is atomic and handles race conditions at the database level - from sqlalchemy.dialects.postgresql import insert - stmt = insert(UserModel.__table__).values( provider_id=provider_id, created_at=datetime.now(timezone.utc), @@ -65,16 +65,52 @@ class UserClient(BaseDBClient): ) return result.scalars().first() + async def _get_user_configuration_row( + self, session, user_id: int, key: str + ) -> UserConfigurationModel | None: + result = await session.execute( + select(UserConfigurationModel).where( + UserConfigurationModel.user_id == user_id, + UserConfigurationModel.key == key, + ) + ) + return result.scalars().first() + + async def get_user_configuration_value(self, user_id: int, key: str) -> dict | None: + """Get the JSON value stored for a user under `key`, or None.""" + async with self.async_session() as session: + row = await self._get_user_configuration_row(session, user_id, key) + return row.configuration if row else None + + async def upsert_user_configuration_value( + self, user_id: int, key: str, value: dict + ) -> dict: + """Create or update the JSON value stored for a user under `key`.""" + async with self.async_session() as session: + stmt = insert(UserConfigurationModel.__table__).values( + user_id=user_id, + key=key, + configuration=value, + ) + stmt = stmt.on_conflict_do_update( + constraint="_user_configuration_key_uc", + set_={"configuration": stmt.excluded.configuration}, + ).returning(UserConfigurationModel.configuration) + try: + result = await session.execute(stmt) + await session.commit() + except Exception as e: + await session.rollback() + raise e + return result.scalar_one() + async def get_user_configurations( self, user_id: int ) -> EffectiveAIModelConfiguration: async with self.async_session() as session: - result = await session.execute( - select(UserConfigurationModel).where( - UserConfigurationModel.user_id == user_id - ) + configuration_obj = await self._get_user_configuration_row( + session, user_id, UserConfigurationKey.MODEL_CONFIGURATION.value ) - configuration_obj = result.scalars().first() if not configuration_obj: return EffectiveAIModelConfiguration() @@ -97,38 +133,18 @@ class UserClient(BaseDBClient): async def update_user_configuration( self, user_id: int, configuration: EffectiveAIModelConfiguration ) -> EffectiveAIModelConfiguration: - async with self.async_session() as session: - result = await session.execute( - select(UserConfigurationModel).where( - UserConfigurationModel.user_id == user_id - ) - ) - configuration_obj = result.scalars().first() - if not configuration_obj: - configuration_obj = UserConfigurationModel( - user_id=user_id, configuration=configuration.model_dump() - ) - session.add(configuration_obj) - else: - configuration_obj.configuration = configuration.model_dump() - try: - await session.commit() - except Exception as e: - await session.rollback() - raise e - await session.refresh(configuration_obj) - return EffectiveAIModelConfiguration.model_validate( - configuration_obj.configuration + value = await self.upsert_user_configuration_value( + user_id, + UserConfigurationKey.MODEL_CONFIGURATION.value, + configuration.model_dump(), ) + return EffectiveAIModelConfiguration.model_validate(value) async def update_user_configuration_last_validated_at(self, user_id: int) -> None: async with self.async_session() as session: - result = await session.execute( - select(UserConfigurationModel).where( - UserConfigurationModel.user_id == user_id - ) + configuration_obj = await self._get_user_configuration_row( + session, user_id, UserConfigurationKey.MODEL_CONFIGURATION.value ) - configuration_obj = result.scalars().first() if not configuration_obj: raise ValueError(f"User configuration with ID {user_id} not found") configuration_obj.last_validated_at = datetime.now() diff --git a/api/db/workflow_run_client.py b/api/db/workflow_run_client.py index 497230ad..3e5137f7 100644 --- a/api/db/workflow_run_client.py +++ b/api/db/workflow_run_client.py @@ -17,6 +17,7 @@ from api.db.models import ( from api.enums import CallType, StorageBackend from api.schemas.workflow import WorkflowRunResponseSchema from api.services.workflow.run_usage_response import format_public_cost_info +from api.utils.recording_artifacts import get_recording_storage_key class WorkflowRunClient(BaseDBClient): @@ -188,13 +189,19 @@ class WorkflowRunClient(BaseDBClient): "workflow_name": run.workflow.name if run.workflow else None, "user_id": run.workflow.user_id if run.workflow else None, "organization_id": organization.id if organization else None, - "organization_name": organization.provider_id - if organization - else None, + "organization_name": ( + organization.provider_id if organization else None + ), "mode": run.mode, "is_completed": run.is_completed, "recording_url": run.recording_url, "transcript_url": run.transcript_url, + "user_recording_url": get_recording_storage_key( + run.extra, "user" + ), + "bot_recording_url": get_recording_storage_key( + run.extra, "bot" + ), "usage_info": run.usage_info, "cost_info": run.cost_info, "initial_context": run.initial_context, @@ -313,6 +320,12 @@ class WorkflowRunClient(BaseDBClient): "is_completed": run.is_completed, "recording_url": run.recording_url, "transcript_url": run.transcript_url, + "user_recording_url": get_recording_storage_key( + run.extra, "user" + ), + "bot_recording_url": get_recording_storage_key( + run.extra, "bot" + ), "cost_info": format_public_cost_info( run.cost_info, run.usage_info ), @@ -340,6 +353,7 @@ class WorkflowRunClient(BaseDBClient): logs: dict | None = None, state: str | None = None, annotations: dict | None = None, + extra: dict | None = None, ) -> WorkflowRunModel: async with self.async_session() as session: # Use SELECT FOR UPDATE to lock the row during the update @@ -362,7 +376,12 @@ class WorkflowRunClient(BaseDBClient): if cost_info: run.cost_info = cost_info if initial_context: - run.initial_context = initial_context + # Merge initial context patches so independent call-start/runtime + # writers do not erase keys stored earlier in the run lifecycle. + run.initial_context = { + **(run.initial_context or {}), + **initial_context, + } if gathered_context: # Lets merge the incoming gathered context keys with the existing ones run.gathered_context = { @@ -374,6 +393,8 @@ class WorkflowRunClient(BaseDBClient): run.logs = {**run.logs, **logs} if annotations: run.annotations = {**run.annotations, **annotations} + if extra: + run.extra = {**run.extra, **extra} if is_completed: run.is_completed = is_completed if state: diff --git a/api/enums.py b/api/enums.py index 6ca1085f..2b8ac637 100644 --- a/api/enums.py +++ b/api/enums.py @@ -96,6 +96,15 @@ class OrganizationConfigurationKey(Enum): MODEL_CONFIGURATION_PREFERENCES = "MODEL_CONFIGURATION_PREFERENCES" # Deprecated; read fallback for old org preferences +class UserConfigurationKey(Enum): + """Keys for the per-user keyed JSON store (user_configurations).""" + + MODEL_CONFIGURATION = ( + "MODEL_CONFIGURATION" # Legacy per-user v1 AI model configuration + ) + ONBOARDING = "ONBOARDING" # Post-signup onboarding state (gate, tooltips, actions) + + class WorkflowStatus(Enum): """Workflow status values""" @@ -165,3 +174,5 @@ class PostHogEvent(str, Enum): AGENT_EMBEDDED = "agent_embedded" SIGNED_UP = "signed_up" SIGNED_IN = "signed_in" + ORGANIZATION_CREATED = "organization_created" + ORGANIZATION_USER_ASSOCIATED = "organization_user_associated" diff --git a/api/pyproject.toml b/api/pyproject.toml index ce4a665e..1dd184b5 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,5 +1,5 @@ [project] name = "dograh-api" -version = "1.35.0" +version = "1.37.0" description = "Backend API for Dograh voice AI platform" requires-python = ">=3.13,<3.14" diff --git a/api/requirements.txt b/api/requirements.txt index 844738d1..0f22cef8 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -16,7 +16,7 @@ msgpack==1.1.2 pgvector==0.4.2 bcrypt==5.0.0 email-validator==2.3.0 -posthog==7.11.1 +posthog==7.19.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 90697f91..4519eca7 100644 --- a/api/routes/campaign.py +++ b/api/routes/campaign.py @@ -375,7 +375,7 @@ async def create_campaign( if workflow_def: try: dto = ReactFlowDTO(**workflow_def) - graph = WorkflowGraph(dto) + graph = WorkflowGraph(dto, skip_instance_constraints_for={"trigger"}) required_vars = graph.get_required_template_variables() if ( diff --git a/api/routes/main.py b/api/routes/main.py index 6de59b1d..067f2ab9 100644 --- a/api/routes/main.py +++ b/api/routes/main.py @@ -72,6 +72,11 @@ class HealthResponse(BaseModel): auth_provider: str turn_enabled: bool force_turn_relay: bool + # Public Stack Auth client config — only populated when auth_provider == "stack". + # The UI reads these at runtime to initialize Stack, so they no longer need to + # be baked into the browser bundle at build time. Both are public values. + stack_project_id: str | None = None + stack_publishable_client_key: str | None = None @router.get("/health", response_model=HealthResponse) @@ -81,12 +86,15 @@ async def health() -> HealthResponse: AUTH_PROVIDER, DEPLOYMENT_MODE, FORCE_TURN_RELAY, + STACK_AUTH_PROJECT_ID, + STACK_PUBLISHABLE_CLIENT_KEY, TURN_SECRET, ) from api.utils.common import get_backend_endpoints logger.debug("Health endpoint called") backend_endpoint, _ = await get_backend_endpoints() + is_stack = AUTH_PROVIDER == "stack" return HealthResponse( status="ok", version=APP_VERSION, @@ -95,4 +103,8 @@ async def health() -> HealthResponse: auth_provider=AUTH_PROVIDER, turn_enabled=bool(TURN_SECRET), force_turn_relay=FORCE_TURN_RELAY, + stack_project_id=STACK_AUTH_PROJECT_ID if is_stack else None, + stack_publishable_client_key=( + STACK_PUBLISHABLE_CLIENT_KEY if is_stack else None + ), ) diff --git a/api/routes/organization.py b/api/routes/organization.py index 8f8e4cbe..d6599bc8 100644 --- a/api/routes/organization.py +++ b/api/routes/organization.py @@ -17,7 +17,10 @@ from api.enums import OrganizationConfigurationKey, PostHogEvent from api.schemas.ai_model_configuration import ( DOGRAH_DEFAULT_LANGUAGE, DOGRAH_DEFAULT_VOICE, + DOGRAH_SPEED_MAX, + DOGRAH_SPEED_MIN, DOGRAH_SPEED_OPTIONS, + DOGRAH_SPEED_STEP, OrganizationAIModelConfigurationResponse, OrganizationAIModelConfigurationV2, ) @@ -38,7 +41,11 @@ from api.schemas.telephony_phone_number import ( PhoneNumberUpdateRequest, ProviderSyncStatus, ) -from api.services.auth.depends import get_user, get_user_with_selected_organization +from api.services.auth.depends import ( + _sync_posthog_organization_mps_billing_v2_status, + get_user, + get_user_with_selected_organization, +) from api.services.configuration.ai_model_configuration import ( check_for_masked_keys_in_ai_model_configuration_v2, compile_ai_model_configuration_v2, @@ -56,6 +63,7 @@ from api.services.configuration.masking import is_mask_of, mask_key, mask_user_c from api.services.configuration.registry import ( DOGRAH_STT_LANGUAGES, REGISTRY, + DograhTTSService, ServiceProviders, ServiceType, ) @@ -210,6 +218,13 @@ async def get_telephony_config_warnings(user: UserModel = Depends(get_user)): # --------------------------------------------------------------------------- +def _dograh_allows_custom_voice() -> bool: + extra = DograhTTSService.model_fields["voice"].json_schema_extra + if isinstance(extra, dict): + return bool(extra.get("allow_custom_input", False)) + return False + + def _byok_provider_schemas(service_type: ServiceType) -> dict[str, dict]: return { provider: model_cls.model_json_schema() @@ -251,7 +266,13 @@ async def get_model_configuration_v2_defaults( return { "dograh": { "voices": [DOGRAH_DEFAULT_VOICE], + "allow_custom_input": _dograh_allows_custom_voice(), "speeds": list(DOGRAH_SPEED_OPTIONS), + "speed_range": { + "min": DOGRAH_SPEED_MIN, + "max": DOGRAH_SPEED_MAX, + "step": DOGRAH_SPEED_STEP, + }, "languages": DOGRAH_STT_LANGUAGES, "defaults": { "voice": DOGRAH_DEFAULT_VOICE, @@ -364,9 +385,10 @@ async def migrate_model_configuration_v2( except ValueError as exc: raise HTTPException(status_code=422, detail=exc.args[0]) + billing_account_status = None if DEPLOYMENT_MODE != "oss": try: - await ensure_hosted_mps_billing_account_v2( + billing_account_status = await ensure_hosted_mps_billing_account_v2( organization_id, created_by=str(user.provider_id), ) @@ -389,6 +411,14 @@ async def migrate_model_configuration_v2( organization_id=organization_id, fallback_user_config=legacy, ) + if DEPLOYMENT_MODE != "oss": + _sync_posthog_organization_mps_billing_v2_status( + organization_id, + uses_mps_billing_v2=bool( + billing_account_status + and billing_account_status.get("billing_mode") == "v2" + ), + ) return await _model_configuration_v2_response( user=user, configuration=configuration, diff --git a/api/routes/organization_usage.py b/api/routes/organization_usage.py index 3912745b..2575a7b0 100644 --- a/api/routes/organization_usage.py +++ b/api/routes/organization_usage.py @@ -14,6 +14,7 @@ from api.services.auth.depends import get_user, get_user_with_selected_organizat from api.services.mps_service_key_client import mps_service_key_client from api.services.reports import generate_usage_runs_report_csv from api.utils.artifacts import artifact_url +from api.utils.recording_artifacts import has_recording_track router = APIRouter(prefix="/organizations") @@ -99,8 +100,12 @@ class WorkflowRunUsageResponse(BaseModel): call_duration_seconds: int recording_url: Optional[str] = None transcript_url: Optional[str] = None + user_recording_url: Optional[str] = None + bot_recording_url: Optional[str] = None recording_public_url: Optional[str] = None transcript_public_url: Optional[str] = None + user_recording_public_url: Optional[str] = None + bot_recording_public_url: Optional[str] = None public_access_token: Optional[str] = None phone_number: Optional[str] = Field( default=None, @@ -308,14 +313,18 @@ async def get_billing_credits( aggregation_key=entry.get("aggregation_key"), usage_event_id=_optional_int(entry.get("usage_event_id")), workflow_run_id=_optional_int(entry.get("workflow_run_id")), - workflow_id=workflow_ids_by_run_id.get( - _optional_int(entry.get("workflow_run_id")) - ) - if entry.get("workflow_run_id") is not None - else None, - billable_quantity=float(entry["billable_quantity"]) - if entry.get("billable_quantity") is not None - else None, + workflow_id=( + workflow_ids_by_run_id.get( + _optional_int(entry.get("workflow_run_id")) + ) + if entry.get("workflow_run_id") is not None + else None + ), + billable_quantity=( + float(entry["billable_quantity"]) + if entry.get("billable_quantity") is not None + else None + ), quantity_unit=entry.get("quantity_unit"), metadata=entry.get("metadata") or {}, created_at=str(entry["created_at"]), @@ -478,6 +487,17 @@ async def get_usage_history( public_access_token, "transcript" ) run["recording_public_url"] = artifact_url(public_access_token, "recording") + run["user_recording_public_url"] = ( + artifact_url(public_access_token, "user_recording") + if has_recording_track(run.get("extra"), "user") + else None + ) + run["bot_recording_public_url"] = ( + artifact_url(public_access_token, "bot_recording") + if has_recording_track(run.get("extra"), "bot") + else None + ) + run.pop("extra", None) return { "runs": runs, diff --git a/api/routes/public_download.py b/api/routes/public_download.py index c84cc244..c2d70455 100644 --- a/api/routes/public_download.py +++ b/api/routes/public_download.py @@ -6,14 +6,16 @@ post-call processing for runs that execute integrations, QA, or campaign reporting. """ -from typing import Literal - from fastapi import APIRouter, HTTPException, Query from fastapi.responses import RedirectResponse from loguru import logger from api.db import db_client from api.services.storage import get_storage_for_backend +from api.utils.recording_artifacts import ( + get_recording_storage_backend, + get_recording_storage_key, +) router = APIRouter(prefix="/public/download") @@ -21,7 +23,7 @@ router = APIRouter(prefix="/public/download") @router.get("/workflow/{token}/{artifact_type}") async def download_workflow_artifact( token: str, - artifact_type: Literal["recording", "transcript"], + artifact_type: str, inline: bool = Query( default=False, description="Display inline in browser instead of download" ), @@ -36,13 +38,15 @@ async def download_workflow_artifact( Args: token: The public access token (UUID format) - artifact_type: Type of artifact - "recording" or "transcript" + artifact_type: Type of artifact - "recording", "transcript", + "user_recording", or "bot_recording" inline: If true, sets Content-Disposition to inline for browser preview Returns: RedirectResponse to the signed URL (302 redirect) Raises: + HTTPException 400: If artifact type is unsupported HTTPException 404: If token is invalid or artifact not found """ # 1. Lookup workflow run by token @@ -52,10 +56,26 @@ async def download_workflow_artifact( raise HTTPException(status_code=404, detail="Invalid or expired token") # 2. Get file path based on artifact type + artifact_storage_backend = None if artifact_type == "recording": file_path = workflow_run.recording_url - else: # transcript + elif artifact_type == "transcript": file_path = workflow_run.transcript_url + elif artifact_type == "user_recording": + file_path = get_recording_storage_key(workflow_run.extra, "user") + artifact_storage_backend = get_recording_storage_backend( + workflow_run.extra, "user" + ) + elif artifact_type == "bot_recording": + file_path = get_recording_storage_key(workflow_run.extra, "bot") + artifact_storage_backend = get_recording_storage_backend( + workflow_run.extra, "bot" + ) + else: + logger.warning( + f"Unsupported artifact type: type={artifact_type}, workflow_run_id={workflow_run.id}" + ) + raise HTTPException(status_code=400, detail="Unsupported artifact type") if not file_path: logger.warning( @@ -68,7 +88,9 @@ async def download_workflow_artifact( # 3. Get storage backend for this workflow run try: - storage = get_storage_for_backend(workflow_run.storage_backend) + storage = get_storage_for_backend( + artifact_storage_backend or workflow_run.storage_backend + ) except ValueError as e: logger.error(f"Invalid storage backend: {workflow_run.storage_backend}") raise HTTPException(status_code=500, detail="Storage configuration error") diff --git a/api/routes/s3_signed_url.py b/api/routes/s3_signed_url.py index f0008ae4..b749f98c 100644 --- a/api/routes/s3_signed_url.py +++ b/api/routes/s3_signed_url.py @@ -40,14 +40,22 @@ class PresignedUploadUrlResponse(BaseModel): router = APIRouter(prefix="/s3", tags=["s3"]) +ORG_SCOPED_STORAGE_PREFIXES = ("campaigns", "knowledge_base") + + def _extract_org_id_from_key(key: str) -> Optional[int]: """Try to extract an organization ID from a storage key. - Matches keys of the form ``{prefix}/{org_id}/...`` where *org_id* is a - positive integer. Returns ``None`` when the pattern does not match. + Matches known org-scoped keys of the form ``{prefix}/{org_id}/...`` where + *org_id* is a positive integer. Returns ``None`` when the pattern does not + match. """ parts = key.split("/") - if len(parts) >= 3 and parts[1].isdigit(): + if ( + len(parts) >= 3 + and parts[0] in ORG_SCOPED_STORAGE_PREFIXES + and parts[1].isdigit() + ): return int(parts[1]) return None @@ -58,15 +66,20 @@ def _extract_legacy_workflow_run_id(key: str) -> Optional[int]: Supports: - ``transcripts/{run_id}.txt`` - ``recordings/{run_id}.wav`` + - ``recordings/{run_id}/user.wav`` + - ``recordings/{run_id}/bot.wav`` Returns ``None`` when the key does not match a legacy pattern. """ if key.startswith("transcripts/") and key.endswith(".txt"): run_id_str = key[len("transcripts/") : -4] - elif key.startswith("recordings/") and key.endswith(".wav"): - run_id_str = key[len("recordings/") : -4] else: - return None + recording_match = re.fullmatch( + r"recordings/(\d+)(?:\.wav|/(?:user|bot)\.wav)", key + ) + if not recording_match: + return None + run_id_str = recording_match.group(1) return int(run_id_str) if run_id_str.isdigit() else None @@ -89,8 +102,13 @@ async def _validate_and_extract_workflow_run_id( """ if key.startswith("transcripts/") and key.endswith(".txt"): run_id_str = key[len("transcripts/") : -4] # strip prefix & suffix - elif key.startswith("recordings/") and key.endswith(".wav"): - run_id_str = key[len("recordings/") : -4] + elif key.startswith("recordings/"): + run_id = _extract_legacy_workflow_run_id(key) + if run_id is None: + raise HTTPException( + status_code=400, detail="Invalid workflow_run_id in key" + ) + return run_id elif allow_special_paths and key.startswith("voicemail_detections/"): return None # Skip validation for these paths else: @@ -159,9 +177,9 @@ async def get_signed_url( """Return a short-lived signed URL for a file stored on S3 / MinIO. Access Control: - * Keys that embed an organization ID (``{prefix}/{org_id}/...``) are - authorized by matching the org_id against the requesting user's - organization. + * Known org-scoped keys (for example ``campaigns/{org_id}/...`` and + ``knowledge_base/{org_id}/...``) are authorized by matching the org_id + against the requesting user's organization. * Legacy keys (``recordings/{run_id}.wav``, ``transcripts/{run_id}.txt``) are authorized via the workflow run they belong to. * Superusers can request any key. diff --git a/api/routes/user.py b/api/routes/user.py index d352f809..47949436 100644 --- a/api/routes/user.py +++ b/api/routes/user.py @@ -9,6 +9,7 @@ from api.db import db_client from api.db.models import ( UserModel, ) +from api.schemas.onboarding_state import OnboardingState, OnboardingStateUpdate from api.services.auth.depends import get_user from api.services.configuration.ai_model_configuration import ( get_resolved_ai_model_configuration, @@ -26,6 +27,10 @@ from api.services.organization_preferences import ( get_organization_preferences, upsert_organization_preferences, ) +from api.services.user_onboarding import ( + get_onboarding_state, + update_onboarding_state, +) router = APIRouter(prefix="/user") @@ -203,6 +208,21 @@ async def update_user_configurations( return masked_config +@router.get("/onboarding-state") +async def get_user_onboarding_state( + user: UserModel = Depends(get_user), +) -> OnboardingState: + return await get_onboarding_state(user.id) + + +@router.put("/onboarding-state") +async def update_user_onboarding_state( + request: OnboardingStateUpdate, + user: UserModel = Depends(get_user), +) -> OnboardingState: + return await update_onboarding_state(user.id, request) + + @router.get("/configurations/user/validate") async def validate_user_configurations( validity_ttl_seconds: int = Query(default=60, ge=0, le=86400), diff --git a/api/routes/webrtc_signaling.py b/api/routes/webrtc_signaling.py index ca8d3038..75ed0482 100644 --- a/api/routes/webrtc_signaling.py +++ b/api/routes/webrtc_signaling.py @@ -19,7 +19,7 @@ import ipaddress import os from datetime import UTC, datetime from enum import Enum -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Set from aiortc import RTCIceServer from aiortc.sdp import candidate_from_sdp @@ -246,6 +246,74 @@ class SignalingManager: def __init__(self): self._connections: Dict[str, WebSocket] = {} self._peer_connections: Dict[str, SmallWebRTCConnection] = {} + self._connection_peer_ids: Dict[str, Set[str]] = {} + self._peer_connection_owners: Dict[str, str] = {} + + def _track_peer_connection( + self, connection_id: str, pc_id: str, pc: SmallWebRTCConnection + ) -> None: + self._peer_connections[pc_id] = pc + self._peer_connection_owners[pc_id] = connection_id + self._connection_peer_ids.setdefault(connection_id, set()).add(pc_id) + + def _forget_peer_connection(self, pc_id: str) -> Optional[str]: + connection_id = self._peer_connection_owners.pop(pc_id, None) + self._peer_connections.pop(pc_id, None) + + if connection_id: + peer_ids = self._connection_peer_ids.get(connection_id) + if peer_ids is not None: + peer_ids.discard(pc_id) + if not peer_ids: + self._connection_peer_ids.pop(connection_id, None) + + return connection_id + + async def _send_json_if_connected( + self, websocket: WebSocket, message: dict + ) -> bool: + if websocket.application_state != WebSocketState.CONNECTED: + return False + + try: + await websocket.send_json(message) + return True + except Exception as e: + logger.debug(f"Failed to send signaling WebSocket message: {e}") + return False + + async def _close_websocket_if_connected( + self, websocket: WebSocket, code: int = 1000, reason: str = "" + ) -> None: + if websocket.application_state != WebSocketState.CONNECTED: + return + + try: + await websocket.close(code=code, reason=reason) + except Exception as e: + logger.debug(f"Failed to close signaling WebSocket: {e}") + + async def _notify_call_ended_and_close_websocket( + self, + websocket: WebSocket, + workflow_run_id: int, + pc_id: str, + reason: str, + ) -> None: + await self._send_json_if_connected( + websocket, + { + "type": "call-ended", + "payload": { + "workflow_run_id": workflow_run_id, + "pc_id": pc_id, + "reason": reason, + }, + }, + ) + await self._close_websocket_if_connected( + websocket, code=1000, reason="call ended" + ) async def handle_websocket( self, @@ -257,35 +325,51 @@ class SignalingManager: """Handle WebSocket connection for signaling.""" await websocket.accept() connection_id = f"{workflow_id}:{workflow_run_id}:{user.id}" - self._connections[connection_id] = websocket + connection_key = f"{connection_id}:{id(websocket)}" + self._connections[connection_key] = websocket try: while True: message = await websocket.receive_json() await self._handle_message( - websocket, message, workflow_id, workflow_run_id, user + websocket, + message, + workflow_id, + workflow_run_id, + user, + connection_key, ) except WebSocketDisconnect: logger.info(f"WebSocket disconnected for {connection_id}") except Exception as e: - logger.error(f"WebSocket error for {connection_id}: {e}") + if websocket.application_state == WebSocketState.DISCONNECTED: + logger.info(f"WebSocket disconnected for {connection_id}") + else: + logger.error(f"WebSocket error for {connection_id}: {e}") finally: # Cleanup - self._connections.pop(connection_id, None) + self._connections.pop(connection_key, None) + peer_ids = list(self._connection_peer_ids.pop(connection_key, set())) # Unregister WebSocket sender for real-time feedback unregister_ws_sender(workflow_run_id) - # Clean up all peer connections for this workflow run + # Clean up peer connections owned by this WebSocket. # Note: In a WebSocket-based signaling approach (vs HTTP PATCH), # we maintain our own connection map instead of relying on # SmallWebRTCRequestHandler's _pcs_map. This is suitable for # multi-worker FastAPI deployments where state cannot be shared. - for pc_id in list(self._peer_connections.keys()): + for pc_id in peer_ids: + self._peer_connection_owners.pop(pc_id, None) pc = self._peer_connections.pop(pc_id, None) if pc: - await pc.disconnect() - logger.debug(f"Disconnected peer connection: {pc_id}") + try: + await pc.disconnect() + logger.debug(f"Disconnected peer connection: {pc_id}") + except Exception as e: + logger.debug( + f"Failed to disconnect peer connection {pc_id}: {e}" + ) async def _handle_message( self, @@ -294,17 +378,20 @@ class SignalingManager: workflow_id: int, workflow_run_id: int, user: UserModel, + connection_key: str, ): """Handle incoming WebSocket messages.""" msg_type = message.get("type") payload = message.get("payload", {}) if msg_type == "offer": - await self._handle_offer(ws, payload, workflow_id, workflow_run_id, user) + await self._handle_offer( + ws, payload, workflow_id, workflow_run_id, user, connection_key + ) elif msg_type == "ice-candidate": - await self._handle_ice_candidate(ws, payload, workflow_run_id) + await self._handle_ice_candidate(payload, connection_key) elif msg_type == "renegotiate": - await self._handle_renegotiation(ws, payload, workflow_id, workflow_run_id) + await self._handle_renegotiation(ws, payload, connection_key) async def _handle_offer( self, @@ -313,6 +400,7 @@ class SignalingManager: workflow_id: int, workflow_run_id: int, user: UserModel, + connection_key: str, ): """Handle offer message and create answer with ICE trickling.""" pc_id = payload.get("pc_id") @@ -320,6 +408,15 @@ class SignalingManager: type_ = payload.get("type") call_context_vars = payload.get("call_context_vars", {}) + if not pc_id or not sdp or not type_: + await ws.send_json( + { + "type": "error", + "payload": {"message": "Missing offer fields"}, + } + ) + return + # Set run context for logging and tracing. org_id must be set before # pc.initialize() so that aiortc's internal tasks inherit it. set_current_run_id(workflow_run_id) @@ -347,7 +444,16 @@ class SignalingManager: ) return - if pc_id and pc_id in self._peer_connections: + if pc_id in self._peer_connections: + if self._peer_connection_owners.get(pc_id) != connection_key: + await ws.send_json( + { + "type": "error", + "payload": {"message": "Peer connection already owned"}, + } + ) + return + # Reuse existing connection logger.info(f"Reusing existing connection for pc_id: {pc_id}") pc = self._peer_connections[pc_id] @@ -379,7 +485,7 @@ class SignalingManager: await pc.initialize(sdp=sdp, type=type_) # Store peer connection using client's pc_id - self._peer_connections[pc_id] = pc + self._track_peer_connection(connection_key, pc_id, pc) # Register WebSocket sender for real-time feedback async def ws_sender(message: dict): @@ -392,7 +498,16 @@ class SignalingManager: @pc.event_handler("closed") async def handle_disconnected(webrtc_connection: SmallWebRTCConnection): logger.info(f"PeerConnection closed: {webrtc_connection.pc_id}") - self._peer_connections.pop(webrtc_connection.pc_id, None) + owner_connection_id = self._forget_peer_connection( + webrtc_connection.pc_id + ) + if owner_connection_id == connection_key: + await self._notify_call_ended_and_close_websocket( + ws, + workflow_run_id, + webrtc_connection.pc_id, + reason="peer_connection_closed", + ) # Start pipeline in background asyncio.create_task( @@ -421,9 +536,7 @@ class SignalingManager: } ) - async def _handle_ice_candidate( - self, ws: WebSocket, payload: dict, workflow_run_id: int - ): + async def _handle_ice_candidate(self, payload: dict, connection_key: str): """Handle incoming ICE candidate from client. Uses SmallWebRTC's native ICE trickling support via add_ice_candidate(). @@ -442,6 +555,9 @@ class SignalingManager: if not pc: logger.warning(f"No peer connection found for pc_id: {pc_id}") return + if self._peer_connection_owners.get(pc_id) != connection_key: + logger.warning(f"Ignoring ICE candidate for unowned pc_id: {pc_id}") + return if candidate_data: candidate_str = candidate_data.get("candidate", "") @@ -466,7 +582,7 @@ class SignalingManager: logger.debug(f"End of ICE candidates for pc_id: {pc_id}") async def _handle_renegotiation( - self, ws: WebSocket, payload: dict, workflow_id: int, workflow_run_id: int + self, ws: WebSocket, payload: dict, connection_key: str ): """Handle renegotiation request.""" pc_id = payload.get("pc_id") @@ -479,6 +595,11 @@ class SignalingManager: {"type": "error", "payload": {"message": "Peer connection not found"}} ) return + if self._peer_connection_owners.get(pc_id) != connection_key: + await ws.send_json( + {"type": "error", "payload": {"message": "Peer connection not found"}} + ) + return pc = self._peer_connections[pc_id] await pc.renegotiate(sdp=sdp, type=type_, restart_pc=restart_pc) diff --git a/api/routes/workflow.py b/api/routes/workflow.py index 06e5fdf9..50b17cf7 100644 --- a/api/routes/workflow.py +++ b/api/routes/workflow.py @@ -15,7 +15,7 @@ from api.db import db_client from api.db.agent_trigger_client import TriggerPathConflictError from api.db.models import UserModel from api.db.workflow_template_client import WorkflowTemplateClient -from api.enums import CallType, PostHogEvent, StorageBackend +from api.enums import CallType, PostHogEvent, StorageBackend, WorkflowStatus from api.schemas.ai_model_configuration import OrganizationAIModelConfigurationV2 from api.schemas.workflow import WorkflowRunResponseSchema from api.sdk_expose import sdk_expose @@ -58,8 +58,15 @@ from api.services.workflow.trigger_paths import ( trigger_path_to_node_id, validate_trigger_paths, ) -from api.services.workflow.workflow_graph import WorkflowGraph +from api.services.workflow.workflow_graph import ( + WorkflowGraph, + validate_node_instance_constraints, +) from api.utils.artifacts import artifact_url +from api.utils.recording_artifacts import ( + get_recording_storage_key, + has_recording_track, +) router = APIRouter(prefix="/workflow") @@ -188,6 +195,27 @@ def _validation_errors_http_exception( ) +def _node_instance_validation_errors( + workflow_definition: Optional[dict], +) -> list[WorkflowError]: + """Validate spec-driven max_instances without requiring a complete draft.""" + if not workflow_definition: + return [] + nodes = workflow_definition.get("nodes") + if not isinstance(nodes, list): + return [] + + node_types = [ + node.get("type") + for node in nodes + if isinstance(node, dict) and isinstance(node.get("type"), str) + ] + return validate_node_instance_constraints( + node_types, + enforce_min_instances=False, + ) + + class CallDispositionCodes(BaseModel): disposition_codes: list[str] = [] @@ -380,6 +408,9 @@ async def create_workflow( trigger_path_issues = validate_trigger_paths(workflow_definition) if trigger_path_issues: raise _trigger_path_validation_http_exception(trigger_path_issues) + instance_errors = _node_instance_validation_errors(workflow_definition) + if instance_errors: + raise _validation_errors_http_exception(instance_errors) # Validate trigger path uniqueness BEFORE creating the workflow so we # don't leave an orphaned workflow record when the trigger conflicts. @@ -574,6 +605,31 @@ async def get_workflow_count( ) +def _validate_status_filter(status: Optional[str]) -> List[str]: + """Parse and validate a workflow ``status`` query filter. + + Accepts a single value or a comma-separated list. Returns the list of + validated status values (empty when no filter was supplied). Any value + outside the ``workflow_status`` enum raises 422 so the request fails as a + clean client error instead of a 500 from the Postgres enum cast. + """ + if status is None or status == "": + return [] + allowed = {s.value for s in WorkflowStatus} + requested = [s.strip() for s in status.split(",")] + invalid = sorted({s for s in requested if s not in allowed}) + if invalid: + invalid_display = ["" if s == "" else s for s in invalid] + raise HTTPException( + status_code=422, + detail=( + f"Invalid workflow status filter: {invalid_display}. " + f"Allowed values: {sorted(allowed)}." + ), + ) + return requested + + @router.get( "/fetch", **sdk_expose( @@ -593,21 +649,22 @@ async def get_workflows( Returns a lightweight response with only essential fields for listing. Use GET /workflow/fetch/{workflow_id} to get full workflow details. """ - # Handle comma-separated status values - if status and "," in status: - # Split comma-separated values and fetch workflows for each status - status_list = [s.strip() for s in status.split(",")] + statuses = _validate_status_filter(status) + if statuses: + # Fetch workflows for each requested status and combine the results. all_workflows = [] - for status_value in status_list: - workflows = await db_client.get_all_workflows_for_listing( - organization_id=user.selected_organization_id, status=status_value + for status_value in statuses: + all_workflows.extend( + await db_client.get_all_workflows_for_listing( + organization_id=user.selected_organization_id, + status=status_value, + ) ) - all_workflows.extend(workflows) workflows = all_workflows else: - # Single status or no status filter + # No status filter workflows = await db_client.get_all_workflows_for_listing( - organization_id=user.selected_organization_id, status=status + organization_id=user.selected_organization_id, status=None ) # Get run counts for all workflows in a single query @@ -816,10 +873,20 @@ async def get_workflows_summary( ), ) -> List[WorkflowSummaryResponse]: """Get minimal workflow information (id and name only) for all workflows""" - workflows = await db_client.get_all_workflows( - organization_id=user.selected_organization_id, - status=status, - ) + statuses = _validate_status_filter(status) + if statuses: + workflows = [] + for status_value in statuses: + workflows.extend( + await db_client.get_all_workflows( + organization_id=user.selected_organization_id, + status=status_value, + ) + ) + else: + workflows = await db_client.get_all_workflows( + organization_id=user.selected_organization_id, status=None + ) return [ WorkflowSummaryResponse(id=workflow.id, name=workflow.name) for workflow in workflows @@ -950,6 +1017,9 @@ async def update_workflow( trigger_path_issues = validate_trigger_paths(workflow_definition) if trigger_path_issues: raise _trigger_path_validation_http_exception(trigger_path_issues) + instance_errors = _node_instance_validation_errors(workflow_definition) + if instance_errors: + raise _validation_errors_http_exception(instance_errors, status_code=409) if workflow_definition: existing_workflow = await db_client.get_workflow( workflow_id, organization_id=user.selected_organization_id @@ -1255,7 +1325,16 @@ async def get_workflow_run( raise HTTPException(status_code=404, detail="Workflow run not found") public_access_token = run.public_access_token - if (run.transcript_url or run.recording_url) and not public_access_token: + user_recording_url = get_recording_storage_key(run.extra, "user") + bot_recording_url = get_recording_storage_key(run.extra, "bot") + has_user_recording = has_recording_track(run.extra, "user") + has_bot_recording = has_recording_track(run.extra, "bot") + if ( + run.transcript_url + or run.recording_url + or has_user_recording + or has_bot_recording + ) and not public_access_token: public_access_token = await db_client.ensure_public_access_token(run.id) return { @@ -1266,8 +1345,20 @@ async def get_workflow_run( "is_completed": run.is_completed, "transcript_url": run.transcript_url, "recording_url": run.recording_url, + "user_recording_url": user_recording_url, + "bot_recording_url": bot_recording_url, "transcript_public_url": artifact_url(public_access_token, "transcript"), "recording_public_url": artifact_url(public_access_token, "recording"), + "user_recording_public_url": ( + artifact_url(public_access_token, "user_recording") + if has_user_recording + else None + ), + "bot_recording_public_url": ( + artifact_url(public_access_token, "bot_recording") + if has_bot_recording + else None + ), "public_access_token": public_access_token, "cost_info": format_public_cost_info(run.cost_info, run.usage_info), "usage_info": format_public_usage_info(run.usage_info), diff --git a/api/schemas/ai_model_configuration.py b/api/schemas/ai_model_configuration.py index c5403b04..05cfcf0b 100644 --- a/api/schemas/ai_model_configuration.py +++ b/api/schemas/ai_model_configuration.py @@ -18,6 +18,9 @@ from api.services.configuration.registry import ( TTSConfig, ) +DOGRAH_SPEED_MIN = 0.5 +DOGRAH_SPEED_MAX = 2.0 +DOGRAH_SPEED_STEP = 0.1 DOGRAH_SPEED_OPTIONS: tuple[float, ...] = (0.8, 1.0, 1.2) DOGRAH_DEFAULT_VOICE = "default" DOGRAH_DEFAULT_LANGUAGE = "multi" @@ -49,16 +52,9 @@ class EffectiveAIModelConfiguration(BaseModel): class DograhManagedAIModelConfiguration(BaseModel): api_key: str voice: str = DOGRAH_DEFAULT_VOICE - speed: float = Field(default=1.0) + speed: float = Field(default=1.0, ge=DOGRAH_SPEED_MIN, le=DOGRAH_SPEED_MAX) language: str = DOGRAH_DEFAULT_LANGUAGE - @model_validator(mode="after") - def validate_speed(self): - if self.speed not in DOGRAH_SPEED_OPTIONS: - allowed = ", ".join(str(speed) for speed in DOGRAH_SPEED_OPTIONS) - raise ValueError(f"Dograh speed must be one of: {allowed}") - return self - class BYOKPipelineAIModelConfiguration(BaseModel): llm: LLMConfig diff --git a/api/schemas/onboarding_state.py b/api/schemas/onboarding_state.py new file mode 100644 index 00000000..689200ca --- /dev/null +++ b/api/schemas/onboarding_state.py @@ -0,0 +1,47 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + + +class OnboardingState(BaseModel): + """Per-user onboarding state, stored under UserConfigurationKey.ONBOARDING. + + Server-authoritative replacement for the browser-localStorage onboarding + store, so the post-signup gate and one-time tooltips hold across devices. + """ + + # Post-signup onboarding form gate: set once on submit/skip. + completed_at: datetime | None = None + skipped: bool = False + # One-time UI affordances (tooltip keys, milestone action keys). Kept as + # free-form strings — the UI owns the vocabulary. + seen_tooltips: list[str] = Field(default_factory=list) + completed_actions: list[str] = Field(default_factory=list) + + +class OnboardingStateUpdate(BaseModel): + """Partial update merged into the stored state. + + Scalars overwrite when supplied; list entries are unioned into the stored + lists, so concurrent updates (e.g. two tabs marking different tooltips) + don't drop each other's items. + """ + + completed_at: datetime | None = None + skipped: bool | None = None + seen_tooltips: list[str] | None = None + completed_actions: list[str] | None = None + + def apply_to(self, state: OnboardingState) -> OnboardingState: + merged = state.model_copy(deep=True) + if self.completed_at is not None: + merged.completed_at = self.completed_at + if self.skipped is not None: + merged.skipped = self.skipped + for tooltip in self.seen_tooltips or []: + if tooltip not in merged.seen_tooltips: + merged.seen_tooltips.append(tooltip) + for action in self.completed_actions or []: + if action not in merged.completed_actions: + merged.completed_actions.append(action) + return merged diff --git a/api/schemas/workflow.py b/api/schemas/workflow.py index ae0a2659..2291e11f 100644 --- a/api/schemas/workflow.py +++ b/api/schemas/workflow.py @@ -15,8 +15,12 @@ class WorkflowRunResponseSchema(BaseModel): is_completed: bool transcript_url: str | None recording_url: str | None + user_recording_url: str | None = None + bot_recording_url: str | None = None transcript_public_url: str | None = None recording_public_url: str | None = None + user_recording_public_url: str | None = None + bot_recording_public_url: str | None = None public_access_token: str | None = None cost_info: Dict[str, Any] | None usage_info: Dict[str, Any] | None = None diff --git a/api/services/auth/depends.py b/api/services/auth/depends.py index 019dbc2f..94efae79 100644 --- a/api/services/auth/depends.py +++ b/api/services/auth/depends.py @@ -13,9 +13,16 @@ from api.schemas.ai_model_configuration import EffectiveAIModelConfiguration from api.services.auth.stack_auth import stackauth from api.services.configuration.registry import ServiceProviders from api.services.mps_billing import ensure_hosted_mps_billing_account_v2 -from api.services.posthog_client import capture_event +from api.services.posthog_client import ( + capture_event, + group_identify, + set_person_properties, +) from api.utils.auth import decode_jwt_token +POSTHOG_ORGANIZATION_GROUP_TYPE = "organization" +POSTHOG_ORGANIZATION_USES_MPS_BILLING_V2_PROPERTY = "uses_mps_billing_v2" + async def get_user( authorization: Annotated[str | None, Header()] = None, @@ -94,6 +101,11 @@ async def get_user( ) = await db_client.get_or_create_organization_by_provider_id( org_provider_id=selected_team_id, user_id=user_model.id ) + if org_was_created: + _sync_created_organization_to_posthog( + organization=organization, + stack_user=stack_user, + ) # Check if user's selected organization differs from the current organization if user_model.selected_organization_id != organization.id: @@ -107,6 +119,13 @@ async def get_user( # Update the user_model object to reflect the change user_model.selected_organization_id = organization.id + _associate_user_with_posthog_organization( + user=user_model, + organization=organization, + stack_user=stack_user, + org_was_created=org_was_created, + ) + # Only create default configuration if organization was just created # This prevents race conditions where multiple concurrent requests # might try to create configurations @@ -156,6 +175,146 @@ async def get_user( return user_model +def _sync_created_organization_to_posthog( + *, + organization, + stack_user: dict | None = None, + created_by_provider_id: str | None = None, + uses_mps_billing_v2: bool | None = None, +) -> None: + """Create/update the PostHog organization group for a newly-created org.""" + try: + organization_id = int(organization.id) + organization_provider_id = getattr(organization, "provider_id", None) + created_by = created_by_provider_id + if created_by is None and stack_user and stack_user.get("id"): + created_by = str(stack_user["id"]) + properties = { + "organization_id": organization_id, + "organization_provider_id": organization_provider_id, + "auth_provider": "stack", + } + if created_by: + properties["created_by_provider_id"] = created_by + if uses_mps_billing_v2 is not None: + properties[POSTHOG_ORGANIZATION_USES_MPS_BILLING_V2_PROPERTY] = ( + uses_mps_billing_v2 + ) + + group_identify( + POSTHOG_ORGANIZATION_GROUP_TYPE, + str(organization_id), + properties, + distinct_id=created_by, + ) + if created_by: + capture_event( + distinct_id=created_by, + event=PostHogEvent.ORGANIZATION_CREATED, + properties=properties, + groups={POSTHOG_ORGANIZATION_GROUP_TYPE: str(organization_id)}, + ) + except Exception: + logger.exception("Failed to sync created organization to PostHog") + + +def _sync_posthog_organization_group_properties( + *, + organization, + uses_mps_billing_v2: bool | None = None, +) -> None: + """Update PostHog organization group properties without creating a person.""" + try: + organization_id = int(organization.id) + properties = { + "organization_id": organization_id, + "organization_provider_id": getattr(organization, "provider_id", None), + "auth_provider": "stack", + } + if uses_mps_billing_v2 is not None: + properties[POSTHOG_ORGANIZATION_USES_MPS_BILLING_V2_PROPERTY] = ( + uses_mps_billing_v2 + ) + + group_identify( + POSTHOG_ORGANIZATION_GROUP_TYPE, + str(organization_id), + properties, + ) + except Exception: + logger.exception("Failed to sync organization group properties to PostHog") + + +def _sync_posthog_organization_mps_billing_v2_status( + organization_id: int, + *, + uses_mps_billing_v2: bool, +) -> None: + """Update the PostHog organization group with current MPS billing status.""" + try: + organization_id = int(organization_id) + group_identify( + POSTHOG_ORGANIZATION_GROUP_TYPE, + str(organization_id), + {POSTHOG_ORGANIZATION_USES_MPS_BILLING_V2_PROPERTY: uses_mps_billing_v2}, + ) + except Exception: + logger.exception("Failed to sync organization billing status to PostHog") + + +def _associate_user_with_posthog_organization( + *, + user: UserModel, + organization, + stack_user: dict | None = None, + user_distinct_id: str | None = None, + org_was_created: bool, + organization_ids: list[int] | None = None, + selected_organization_id: int | None = None, + selected_organization_provider_id: str | None = None, +) -> None: + """Attach the Stack user to the PostHog organization group.""" + try: + organization_id = int(organization.id) + organization_provider_id = getattr(organization, "provider_id", None) + if user_distinct_id is None: + if stack_user and stack_user.get("id"): + user_distinct_id = str(stack_user["id"]) + else: + user_distinct_id = str(user.provider_id) + selected_org_id = selected_organization_id or organization_id + selected_org_provider_id = ( + selected_organization_provider_id or organization_provider_id + ) + person_properties = { + "user_id": user.id, + "user_provider_id": user_distinct_id, + "selected_organization_id": selected_org_id, + "selected_organization_provider_id": selected_org_provider_id, + } + if organization_ids is not None: + person_properties["organization_ids"] = organization_ids + if user.email: + person_properties["email"] = user.email + set_person_properties(user_distinct_id, person_properties) + event_properties = { + "user_id": user.id, + "organization_id": organization_id, + "organization_provider_id": organization_provider_id, + "auth_provider": "stack", + "organization_was_created": org_was_created, + } + + capture_event( + distinct_id=user_distinct_id, + event=PostHogEvent.ORGANIZATION_USER_ASSOCIATED, + properties=event_properties, + groups={POSTHOG_ORGANIZATION_GROUP_TYPE: str(organization_id)}, + ) + except Exception: + logger.exception("Failed to associate user with PostHog organization") + + async def get_user_with_selected_organization( user: Annotated[UserModel, Depends(get_user)], ) -> UserModel: diff --git a/api/services/configuration/ai_model_configuration.py b/api/services/configuration/ai_model_configuration.py index c5331515..a6979528 100644 --- a/api/services/configuration/ai_model_configuration.py +++ b/api/services/configuration/ai_model_configuration.py @@ -16,7 +16,8 @@ from api.enums import OrganizationConfigurationKey from api.schemas.ai_model_configuration import ( DOGRAH_DEFAULT_LANGUAGE, DOGRAH_DEFAULT_VOICE, - DOGRAH_SPEED_OPTIONS, + DOGRAH_SPEED_MAX, + DOGRAH_SPEED_MIN, BYOKAIModelConfiguration, BYOKPipelineAIModelConfiguration, BYOKRealtimeAIModelConfiguration, @@ -436,7 +437,11 @@ def _convert_any_dograh_legacy_configuration( dograh_key: str, ) -> OrganizationAIModelConfigurationV2: speed = getattr(configuration.tts, "speed", 1.0) - if speed not in DOGRAH_SPEED_OPTIONS: + try: + speed = float(speed) + except (TypeError, ValueError): + speed = 1.0 + if not DOGRAH_SPEED_MIN <= speed <= DOGRAH_SPEED_MAX: speed = 1.0 return OrganizationAIModelConfigurationV2( mode="dograh", diff --git a/api/services/configuration/check_validity.py b/api/services/configuration/check_validity.py index b1996879..3e97709c 100644 --- a/api/services/configuration/check_validity.py +++ b/api/services/configuration/check_validity.py @@ -1,5 +1,6 @@ from typing import Optional, TypedDict +import httpx import openai from deepgram import DeepgramClient from groq import Groq @@ -38,6 +39,7 @@ class UserConfigurationValidator: ServiceProviders.DEEPGRAM.value: self._check_deepgram_api_key, ServiceProviders.GROQ.value: self._check_groq_api_key, ServiceProviders.OPENROUTER.value: self._check_openrouter_api_key, + ServiceProviders.INWORLD.value: self._check_inworld_api_key, ServiceProviders.ELEVENLABS.value: self._validate_elevenlabs_api_key, ServiceProviders.GOOGLE.value: self._check_google_api_key, ServiceProviders.AZURE.value: self._check_azure_api_key, @@ -49,6 +51,7 @@ class UserConfigurationValidator: ServiceProviders.CAMB.value: self._check_camb_api_key, ServiceProviders.AWS_BEDROCK.value: self._check_aws_bedrock_api_key, ServiceProviders.SPEACHES.value: self._check_speaches_api_key, + ServiceProviders.HUGGINGFACE.value: self._check_huggingface_api_key, ServiceProviders.GOOGLE_VERTEX.value: self._check_google_vertex_llm_api_key, ServiceProviders.OPENAI_REALTIME.value: self._check_openai_api_key, ServiceProviders.GROK_REALTIME.value: self._check_grok_realtime_api_key, @@ -60,6 +63,7 @@ class UserConfigurationValidator: ServiceProviders.GLADIA.value: self._check_gladia_api_key, ServiceProviders.RIME.value: self._check_rime_api_key, ServiceProviders.MINIMAX.value: self._check_minimax_api_key, + ServiceProviders.SMALLEST.value: self._check_smallest_api_key, } async def validate( @@ -343,6 +347,32 @@ class UserConfigurationValidator: def _check_openrouter_api_key(self, model: str, api_key: str) -> bool: return True + def _check_inworld_api_key(self, model: str, api_key: str) -> bool: + try: + response = httpx.get( + "https://api.inworld.ai/voices/v1/voices", + headers={"Authorization": f"Basic {api_key}"}, + params={"pageSize": 1}, + timeout=10.0, + ) + response.raise_for_status() + return True + except httpx.HTTPStatusError as exc: + if exc.response.status_code in (401, 403): + raise ValueError( + "Invalid Inworld API key. The key was rejected by the Inworld API. " + "Please verify that your API key is correct, active, and has voice read access." + ) from exc + raise ValueError( + "The Inworld API returned an error while validating the API key. " + "Please try again later." + ) from exc + except httpx.RequestError as exc: + raise ValueError( + "Could not connect to the Inworld API. Please check your network connection " + "and try again." + ) from exc + def _check_grok_realtime_api_key(self, model: str, api_key: str) -> bool: return True @@ -360,6 +390,14 @@ class UserConfigurationValidator: raise ValueError("base_url is required for Speaches services") return True + def _check_huggingface_api_key(self, model: str, api_key: str) -> bool: + if not api_key.startswith("hf_"): + raise ValueError( + "Invalid Hugging Face API token format. Use a token that starts with " + "'hf_' and has Inference Providers permission." + ) + return True + def _check_google_vertex_realtime_api_key(self, model: str, service_config) -> bool: if not getattr(service_config, "project_id", None): raise ValueError("project_id is required for Google Vertex Realtime") @@ -389,6 +427,7 @@ class UserConfigurationValidator: return True def _check_minimax_api_key(self, model: str, api_key: str) -> bool: - # MiniMax doesn't publish a cheap key-validation endpoint; trust the key - # at save time and surface auth errors at first call (same as Rime/Sarvam). + return True + + def _check_smallest_api_key(self, model: str, api_key: str) -> bool: return True diff --git a/api/services/configuration/options/__init__.py b/api/services/configuration/options/__init__.py index 1e3294ae..c30b128c 100644 --- a/api/services/configuration/options/__init__.py +++ b/api/services/configuration/options/__init__.py @@ -9,7 +9,13 @@ from .azure import ( AZURE_SPEECH_TTS_LANGUAGES, AZURE_SPEECH_TTS_VOICES, ) -from .deepgram import DEEPGRAM_LANGUAGES, DEEPGRAM_STT_MODELS +from .deepgram import ( + DEEPGRAM_FLUX_MODELS, + DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGE_OPTIONS, + DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGES, + DEEPGRAM_LANGUAGES, + DEEPGRAM_STT_MODELS, +) from .gladia import GLADIA_STT_LANGUAGES, GLADIA_STT_MODELS from .google import ( GOOGLE_MODELS, @@ -35,6 +41,12 @@ from .sarvam import ( SARVAM_V2_VOICES, SARVAM_V3_VOICES, ) +from .smallest import ( + SMALLEST_TTS_LANGUAGES, + SMALLEST_TTS_MODELS, + SMALLEST_TTS_PRO_VOICES, + SMALLEST_TTS_VOICES, +) from .speechmatics import SPEECHMATICS_STT_LANGUAGES __all__ = [ @@ -47,6 +59,9 @@ __all__ = [ "AZURE_SPEECH_STT_LANGUAGES", "AZURE_SPEECH_TTS_LANGUAGES", "AZURE_SPEECH_TTS_VOICES", + "DEEPGRAM_FLUX_MODELS", + "DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGES", + "DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGE_OPTIONS", "DEEPGRAM_LANGUAGES", "DEEPGRAM_STT_MODELS", "GLADIA_STT_LANGUAGES", @@ -71,5 +86,9 @@ __all__ = [ "SARVAM_TTS_MODELS", "SARVAM_V2_VOICES", "SARVAM_V3_VOICES", + "SMALLEST_TTS_LANGUAGES", + "SMALLEST_TTS_MODELS", + "SMALLEST_TTS_PRO_VOICES", + "SMALLEST_TTS_VOICES", "SPEECHMATICS_STT_LANGUAGES", ] diff --git a/api/services/configuration/options/deepgram.py b/api/services/configuration/options/deepgram.py index fffa564e..1ab42a01 100644 --- a/api/services/configuration/options/deepgram.py +++ b/api/services/configuration/options/deepgram.py @@ -1,4 +1,21 @@ -DEEPGRAM_STT_MODELS = ("nova-3-general", "flux-general-en", "flux-general-multi") +DEEPGRAM_FLUX_MODELS = ("flux-general-en", "flux-general-multi") +DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGES = ( + "de", + "en", + "es", + "fr", + "hi", + "it", + "ja", + "nl", + "pt", + "ru", +) +DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGE_OPTIONS = ( + "multi", + *DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGES, +) +DEEPGRAM_STT_MODELS = ("nova-3-general", *DEEPGRAM_FLUX_MODELS) DEEPGRAM_LANGUAGES = ( "multi", "ar", diff --git a/api/services/configuration/options/smallest.py b/api/services/configuration/options/smallest.py new file mode 100644 index 00000000..70b90f56 --- /dev/null +++ b/api/services/configuration/options/smallest.py @@ -0,0 +1,45 @@ +SMALLEST_TTS_MODELS = ("lightning_v3.1", "lightning_v3.1_pro") +SMALLEST_TTS_VOICES = ( + "sophia", + "avery", + "liam", + "lucas", + "olivia", + "ryan", + "freya", + "william", + "devansh", + "arjun", + "niharika", + "maya", + "dhruv", + "mia", + "maithili", +) +# Premium voices for lightning_v3.1_pro (American, British, Indian accents; English + Hindi only) +SMALLEST_TTS_PRO_VOICES = ( + "meher", + "rhea", + "aviraj", + "cressida", + "willow", + "maverick", +) +SMALLEST_TTS_LANGUAGES = ( + "en", + "hi", + "fr", + "de", + "es", + "it", + "nl", + "pl", + "ru", + "ar", + "bn", + "gu", + "he", + "kn", + "mr", + "ta", +) diff --git a/api/services/configuration/registry.py b/api/services/configuration/registry.py index a0a74546..7e269080 100644 --- a/api/services/configuration/registry.py +++ b/api/services/configuration/registry.py @@ -14,6 +14,7 @@ from api.services.configuration.options import ( AZURE_SPEECH_STT_LANGUAGES, AZURE_SPEECH_TTS_LANGUAGES, AZURE_SPEECH_TTS_VOICES, + DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGE_OPTIONS, DEEPGRAM_LANGUAGES, DEEPGRAM_STT_MODELS, GLADIA_STT_LANGUAGES, @@ -38,6 +39,10 @@ from api.services.configuration.options import ( SARVAM_TTS_MODELS, SARVAM_V2_VOICES, SARVAM_V3_VOICES, + SMALLEST_TTS_LANGUAGES, + SMALLEST_TTS_MODELS, + SMALLEST_TTS_PRO_VOICES, + SMALLEST_TTS_VOICES, SPEECHMATICS_STT_LANGUAGES, ) from api.services.configuration.options.google import GOOGLE_VERTEX_MODELS @@ -56,6 +61,7 @@ class ServiceProviders(str, Enum): DEEPGRAM = "deepgram" GROQ = "groq" OPENROUTER = "openrouter" + INWORLD = "inworld" CARTESIA = "cartesia" # NEUPHONIC = "neuphonic" ELEVENLABS = "elevenlabs" @@ -68,6 +74,7 @@ class ServiceProviders(str, Enum): CAMB = "camb" AWS_BEDROCK = "aws_bedrock" SPEACHES = "speaches" + HUGGINGFACE = "huggingface" ASSEMBLYAI = "assemblyai" GLADIA = "gladia" RIME = "rime" @@ -79,6 +86,7 @@ class ServiceProviders(str, Enum): GOOGLE_REALTIME = "google_realtime" GOOGLE_VERTEX_REALTIME = "google_vertex_realtime" AZURE_REALTIME = "azure_realtime" + SMALLEST = "smallest" class BaseServiceConfiguration(BaseModel): @@ -87,6 +95,7 @@ class BaseServiceConfiguration(BaseModel): ServiceProviders.DEEPGRAM, ServiceProviders.GROQ, ServiceProviders.OPENROUTER, + ServiceProviders.INWORLD, ServiceProviders.ELEVENLABS, ServiceProviders.GOOGLE, ServiceProviders.AZURE, @@ -94,6 +103,7 @@ class BaseServiceConfiguration(BaseModel): ServiceProviders.DOGRAH, ServiceProviders.AWS_BEDROCK, ServiceProviders.SPEACHES, + ServiceProviders.HUGGINGFACE, ServiceProviders.ASSEMBLYAI, ServiceProviders.GLADIA, ServiceProviders.RIME, @@ -106,6 +116,7 @@ class BaseServiceConfiguration(BaseModel): ServiceProviders.GOOGLE_VERTEX_REALTIME, ServiceProviders.AZURE_REALTIME, ServiceProviders.SARVAM, + ServiceProviders.SMALLEST, ] api_key: str | list[str] @@ -240,6 +251,14 @@ GOOGLE_VERTEX_REALTIME_PROVIDER_MODEL_CONFIG = provider_model_config( DEEPGRAM_PROVIDER_MODEL_CONFIG = provider_model_config("Deepgram") ELEVENLABS_PROVIDER_MODEL_CONFIG = provider_model_config("ElevenLabs") CARTESIA_PROVIDER_MODEL_CONFIG = provider_model_config("Cartesia") +INWORLD_PROVIDER_MODEL_CONFIG = provider_model_config( + "Inworld", + description=( + "Inworld AI streaming text-to-speech with built-in and cloned voices. " + "Defaults to the Ashley system voice on inworld-tts-2." + ), + provider_docs_url="https://docs.inworld.ai/tts/tts", +) SARVAM_PROVIDER_MODEL_CONFIG = provider_model_config("Sarvam") CAMB_PROVIDER_MODEL_CONFIG = provider_model_config("Camb.ai") RIME_PROVIDER_MODEL_CONFIG = provider_model_config("Rime") @@ -255,6 +274,11 @@ SPEACHES_PROVIDER_MODEL_CONFIG = provider_model_config( ), provider_docs_url="https://github.com/speaches-ai/speaches", ) +HUGGINGFACE_PROVIDER_MODEL_CONFIG = provider_model_config( + "Hugging Face", + description="Hosted Hugging Face Inference Providers API for usage-based inference.", + provider_docs_url="https://huggingface.co/docs/inference-providers/en/index", +) AZURE_SPEECH_PROVIDER_MODEL_CONFIG = provider_model_config( "Azure Speech Services", description="Azure Cognitive Services Speech — TTS and STT via the Azure Speech SDK.", @@ -471,6 +495,35 @@ class SpeachesLLMConfiguration(BaseLLMConfiguration): ) +HUGGINGFACE_LLM_MODELS = [ + "openai/gpt-oss-120b:cerebras", + "deepseek-ai/DeepSeek-R1:fastest", + "Qwen/Qwen3-Coder-480B-A35B-Instruct:fastest", +] + + +@register_llm +class HuggingFaceLLMConfiguration(BaseLLMConfiguration): + model_config = HUGGINGFACE_PROVIDER_MODEL_CONFIG + provider: Literal[ServiceProviders.HUGGINGFACE] = ServiceProviders.HUGGINGFACE + model: str = Field( + default="openai/gpt-oss-120b:cerebras", + description="Hugging Face chat-completion model identifier, optionally with provider suffix.", + json_schema_extra={ + "examples": HUGGINGFACE_LLM_MODELS, + "allow_custom_input": True, + }, + ) + base_url: str = Field( + default="https://router.huggingface.co/v1", + description="Hugging Face OpenAI-compatible chat-completions router base URL.", + ) + bill_to: str | None = Field( + default=None, + description="Optional Hugging Face organization or user to bill using X-HF-Bill-To.", + ) + + MINIMAX_MODELS = [ "MiniMax-M2.7", "MiniMax-M2.7-highspeed", @@ -741,6 +794,7 @@ LLMConfig = Annotated[ DograhLLMService, AWSBedrockLLMConfiguration, SpeachesLLMConfiguration, + HuggingFaceLLMConfiguration, MiniMaxLLMConfiguration, SarvamLLMConfiguration, ], @@ -907,11 +961,15 @@ class DograhTTSService(BaseTTSConfiguration): voice: str = Field( default="default", description="Voice preset.", + json_schema_extra={"allow_custom_input": True}, ) speed: float = Field(default=1.0, ge=0.5, le=2.0, description="Speed of the voice.") CARTESIA_TTS_MODELS = ["sonic-3.5", "sonic-3"] +INWORLD_TTS_MODELS = ["inworld-tts-2"] +INWORLD_TTS_VOICES = ["Ashley"] +INWORLD_TTS_LANGUAGES = ["en-US"] @register_tts @@ -934,6 +992,51 @@ class CartesiaTTSConfiguration(BaseTTSConfiguration): le=2.0, description="Volume multiplier for generated speech.", ) + language: str = Field( + default="en", + description="Cartesia language code for TTS synthesis (e.g. 'en', 'tr', 'fr', 'de').", + json_schema_extra={"allow_custom_input": True}, + ) + + +@register_tts +class InworldTTSConfiguration(BaseTTSConfiguration): + model_config = INWORLD_PROVIDER_MODEL_CONFIG + provider: Literal[ServiceProviders.INWORLD] = ServiceProviders.INWORLD + model: str = Field( + default="inworld-tts-2", + description="Inworld TTS model.", + json_schema_extra={"examples": INWORLD_TTS_MODELS, "allow_custom_input": True}, + ) + voice: str = Field( + default="Ashley", + description=( + "Inworld voice ID. Use Ashley for the default warm English voice, " + "or a workspace voice ID for a cloned/custom voice." + ), + json_schema_extra={"examples": INWORLD_TTS_VOICES, "allow_custom_input": True}, + ) + language: str = Field( + default="en-US", + description="BCP-47 language code for synthesis.", + json_schema_extra={ + "examples": INWORLD_TTS_LANGUAGES, + "allow_custom_input": True, + }, + ) + speed: float = Field( + default=1.0, + ge=0.25, + le=4.0, + description="Speech speed multiplier.", + ) + delivery_mode: Literal["STABLE", "BALANCED", "CREATIVE"] = Field( + default="BALANCED", + description=( + "Controls stability versus expressiveness for inworld-tts-2 " + "(STABLE, BALANCED, or CREATIVE)." + ), + ) @register_tts @@ -947,9 +1050,10 @@ class SarvamTTSConfiguration(BaseTTSConfiguration): ) voice: str = Field( default="anushka", - description="Sarvam voice name; must match the selected model's voice list.", + description="Sarvam voice name or custom voice ID.", json_schema_extra={ "examples": SARVAM_V2_VOICES, + "allow_custom_input": True, "model_options": { "bulbul:v2": SARVAM_V2_VOICES, "bulbul:v3": SARVAM_V3_VOICES, @@ -961,6 +1065,12 @@ class SarvamTTSConfiguration(BaseTTSConfiguration): description="BCP-47 Indian-language code (e.g. hi-IN, en-IN).", json_schema_extra={"examples": SARVAM_LANGUAGES}, ) + speed: float = Field( + default=1.0, + ge=0.5, + le=2.0, + description="Speech speed multiplier.", + ) CAMB_TTS_MODELS = ["mars-flash", "mars-pro", "mars-instruct"] @@ -1120,6 +1230,50 @@ class AzureSpeechTTSConfiguration(BaseTTSConfiguration): ) +SMALLEST_PROVIDER_MODEL_CONFIG = provider_model_config( + "Smallest AI", + description="Smallest AI ultralow-latency TTS (Waves) and STT (Pulse) APIs.", + provider_docs_url="https://smallest.ai/docs", +) + + +@register_tts +class SmallestAITTSConfiguration(BaseTTSConfiguration): + model_config = SMALLEST_PROVIDER_MODEL_CONFIG + provider: Literal[ServiceProviders.SMALLEST] = ServiceProviders.SMALLEST + model: str = Field( + default="lightning_v3.1", + description="Smallest AI TTS model. lightning_v3.1_pro is the premium pool (American, British, Indian accents); lightning_v3.1 is the standard pool with 217 voices across 12 languages.", + json_schema_extra={"examples": SMALLEST_TTS_MODELS}, + ) + voice: str = Field( + default="sophia", + description="Smallest AI voice ID. Available voices differ by model: lightning_v3.1 has a broad multilingual pool; lightning_v3.1_pro has premium American, British, and Indian accent voices (English + Hindi only).", + json_schema_extra={ + "examples": list(SMALLEST_TTS_VOICES), + "allow_custom_input": True, + "model_options": { + "lightning_v3.1": list(SMALLEST_TTS_VOICES), + "lightning_v3.1_pro": list(SMALLEST_TTS_PRO_VOICES), + }, + }, + ) + language: str = Field( + default="en", + description="ISO 639-1 language code for synthesis.", + json_schema_extra={ + "examples": SMALLEST_TTS_LANGUAGES, + "allow_custom_input": True, + }, + ) + speed: float = Field( + default=1.0, + ge=0.5, + le=2.0, + description="Speech speed multiplier (0.5 to 2.0).", + ) + + TTSConfig = Annotated[ Union[ DeepgramTTSConfiguration, @@ -1127,6 +1281,7 @@ TTSConfig = Annotated[ OpenAITTSService, ElevenlabsTTSConfiguration, CartesiaTTSConfiguration, + InworldTTSConfiguration, DograhTTSService, SarvamTTSConfiguration, CambTTSConfiguration, @@ -1134,6 +1289,7 @@ TTSConfig = Annotated[ SpeachesTTSConfiguration, MiniMaxTTSConfiguration, AzureSpeechTTSConfiguration, + SmallestAITTSConfiguration, ], Field(discriminator="provider"), ] @@ -1152,12 +1308,16 @@ class DeepgramSTTConfiguration(BaseSTTConfiguration): ) language: str = Field( default="multi", - description="Language code; 'multi' enables auto-detect (Nova-3 only).", + description=( + "Language code. 'multi' enables Nova-3 auto-detect and omits " + "language hints for Flux multilingual auto-detect." + ), json_schema_extra={ "examples": DEEPGRAM_LANGUAGES, "model_options": { "nova-3-general": DEEPGRAM_LANGUAGES, "flux-general-en": ("en",), + "flux-general-multi": DEEPGRAM_FLUX_MULTILINGUAL_LANGUAGE_OPTIONS, }, }, ) @@ -1334,6 +1494,38 @@ class SpeachesSTTConfiguration(BaseSTTConfiguration): ) +HUGGINGFACE_STT_MODELS = [ + "openai/whisper-large-v3-turbo", + "openai/whisper-large-v3", +] + + +@register_stt +class HuggingFaceSTTConfiguration(BaseSTTConfiguration): + model_config = HUGGINGFACE_PROVIDER_MODEL_CONFIG + provider: Literal[ServiceProviders.HUGGINGFACE] = ServiceProviders.HUGGINGFACE + model: str = Field( + default="openai/whisper-large-v3-turbo", + description="Hugging Face ASR model identifier served through Inference Providers.", + json_schema_extra={ + "examples": HUGGINGFACE_STT_MODELS, + "allow_custom_input": True, + }, + ) + base_url: str = Field( + default="https://router.huggingface.co/hf-inference", + description="Hugging Face Inference Providers router base URL.", + ) + bill_to: str | None = Field( + default=None, + description="Optional Hugging Face organization or user to bill using X-HF-Bill-To.", + ) + return_timestamps: bool = Field( + default=False, + description="Request timestamp chunks when supported by the selected provider/model.", + ) + + ASSEMBLYAI_STT_MODELS = ["u3-rt-pro"] ASSEMBLYAI_STT_LANGUAGES = ["en", "es", "de", "fr", "pt", "it"] @@ -1396,6 +1588,62 @@ class AzureSpeechSTTConfiguration(BaseSTTConfiguration): ) +SMALLEST_STT_MODELS = ["pulse"] +SMALLEST_STT_LANGUAGES = [ + "en", + "hi", + "fr", + "de", + "es", + "it", + "nl", + "pl", + "ru", + "pt", + "bn", + "gu", + "kn", + "ml", + "mr", + "ta", + "te", + "pa", + "or", + "bg", + "cs", + "da", + "et", + "fi", + "hu", + "lt", + "lv", + "mt", + "ro", + "sk", + "sv", + "uk", +] + + +@register_stt +class SmallestAISTTConfiguration(BaseSTTConfiguration): + model_config = SMALLEST_PROVIDER_MODEL_CONFIG + provider: Literal[ServiceProviders.SMALLEST] = ServiceProviders.SMALLEST + model: str = Field( + default="pulse", + description="Smallest AI STT model. Supports 38 languages with real-time streaming.", + json_schema_extra={"examples": SMALLEST_STT_MODELS}, + ) + language: str = Field( + default="en", + description="ISO 639-1 language code for transcription.", + json_schema_extra={ + "examples": SMALLEST_STT_LANGUAGES, + "allow_custom_input": True, + }, + ) + + STTConfig = Annotated[ Union[ DeepgramSTTConfiguration, @@ -1406,9 +1654,11 @@ STTConfig = Annotated[ SpeechmaticsSTTConfiguration, SarvamSTTConfiguration, SpeachesSTTConfiguration, + HuggingFaceSTTConfiguration, AssemblyAISTTConfiguration, GladiaSTTConfiguration, AzureSpeechSTTConfiguration, + SmallestAISTTConfiguration, ], Field(discriminator="provider"), ] diff --git a/api/services/integrations/tuner/node.py b/api/services/integrations/tuner/node.py index d6037304..213ae76c 100644 --- a/api/services/integrations/tuner/node.py +++ b/api/services/integrations/tuner/node.py @@ -40,10 +40,7 @@ from api.services.workflow.node_specs.model_spec import ( ) ], graph_constraints=GraphConstraints( - min_incoming=0, - max_incoming=0, - min_outgoing=0, - max_outgoing=0, + min_incoming=0, max_incoming=0, min_outgoing=0, max_outgoing=0, max_instances=1 ), property_order=( "name", diff --git a/api/services/mps_service_key_client.py b/api/services/mps_service_key_client.py index 5f90380f..f37d2e1c 100644 --- a/api/services/mps_service_key_client.py +++ b/api/services/mps_service_key_client.py @@ -512,7 +512,7 @@ class MPSServiceKeyClient: if response.status_code == 200: return response.json() - logger.error( + logger.warning( "Failed to authorize MPS workflow run start: " f"{response.status_code} - {response.text}" ) @@ -601,16 +601,15 @@ class MPSServiceKeyClient: if response.status_code == 200: return response.json() - should_retry = ( - response.status_code == 409 - and "usage_not_ready" in response.text - and attempt < max_attempts + usage_not_ready = ( + response.status_code == 409 and "usage_not_ready" in response.text ) - if should_retry: + if usage_not_ready and attempt < max_attempts: await asyncio.sleep(attempt) continue - logger.error( + log = logger.warning if usage_not_ready else logger.error + log( "Failed to report platform usage: " f"{response.status_code} - {response.text}" ) diff --git a/api/services/pipecat/event_handlers.py b/api/services/pipecat/event_handlers.py index 5ce82a9d..bb66d19f 100644 --- a/api/services/pipecat/event_handlers.py +++ b/api/services/pipecat/event_handlers.py @@ -9,8 +9,8 @@ from api.services.integrations import IntegrationRuntimeSession from api.services.pipecat.audio_config import AudioConfig from api.services.pipecat.audio_playback import play_audio_loop from api.services.pipecat.in_memory_buffers import ( - InMemoryAudioBuffer, InMemoryLogsBuffer, + InMemoryRecordingBuffers, ) from api.services.pipecat.pipeline_metrics_aggregator import PipelineMetricsAggregator from api.services.pipecat.tracing_config import get_trace_url @@ -40,11 +40,11 @@ async def _capture_call_event( "workflow_run_id": workflow_run_id, "workflow_id": workflow_run.workflow_id if workflow_run else None, "call_type": workflow_run.mode if workflow_run else None, - "call_direction": (workflow_run.initial_context or {}).get( - "direction", "outbound" - ) - if workflow_run - else None, + "call_direction": ( + (workflow_run.initial_context or {}).get("direction", "outbound") + if workflow_run + else None + ), } if extra_properties: properties.update(extra_properties) @@ -73,7 +73,7 @@ def register_event_handlers( """Register all event handlers for transport and task events. Returns: - in_memory_audio_buffer for use by other handlers. + In-memory recording buffers for use by other handlers. """ # Initialize in-memory buffers with proper audio configuration sample_rate = audio_config.pipeline_sample_rate if audio_config else 16000 @@ -84,7 +84,7 @@ def register_event_handlers( f"with sample_rate={sample_rate}Hz, channels={num_channels}" ) - in_memory_audio_buffer = InMemoryAudioBuffer( + in_memory_audio_buffers = InMemoryRecordingBuffers( workflow_run_id=workflow_run_id, sample_rate=sample_rate, num_channels=num_channels, @@ -363,14 +363,32 @@ def register_event_handlers( # Write buffers to temp files and enqueue combined processing task audio_temp_path = None + user_audio_temp_path = None + bot_audio_temp_path = None transcript_temp_path = None try: - if not in_memory_audio_buffer.is_empty: - audio_temp_path = await in_memory_audio_buffer.write_to_temp_file() + if not in_memory_audio_buffers.mixed.is_empty: + audio_temp_path = ( + await in_memory_audio_buffers.mixed.write_to_temp_file() + ) else: logger.debug("Audio buffer is empty, skipping upload") + if not in_memory_audio_buffers.user.is_empty: + user_audio_temp_path = ( + await in_memory_audio_buffers.user.write_to_temp_file() + ) + else: + logger.debug("User audio buffer is empty, skipping upload") + + if not in_memory_audio_buffers.bot.is_empty: + bot_audio_temp_path = ( + await in_memory_audio_buffers.bot.write_to_temp_file() + ) + else: + logger.debug("Bot audio buffer is empty, skipping upload") + transcript_temp_path = in_memory_logs_buffer.write_transcript_to_temp_file() if not transcript_temp_path: logger.debug("No transcript events in logs buffer, skipping upload") @@ -385,16 +403,18 @@ def register_event_handlers( workflow_run_id, audio_temp_path, transcript_temp_path, + user_audio_temp_path, + bot_audio_temp_path, ) # Return the buffer so it can be passed to other handlers - return in_memory_audio_buffer + return in_memory_audio_buffers def register_audio_data_handler( audio_buffer: AudioBufferProcessor, workflow_run_id, - in_memory_buffer: InMemoryAudioBuffer, + in_memory_buffers: InMemoryRecordingBuffers, ): """Register event handler for audio data""" logger.info(f"Registering audio data handler for workflow run {workflow_run_id}") @@ -404,9 +424,19 @@ def register_audio_data_handler( if not audio: return - # Use in-memory buffer try: - await in_memory_buffer.append(audio) + await in_memory_buffers.mixed.append(audio) except MemoryError as e: - logger.error(f"Memory buffer full: {e}") - # Could implement overflow to disk here if needed + logger.error(f"Mixed audio buffer full: {e}") + + @audio_buffer.event_handler("on_track_audio_data") + async def on_track_audio_data( + buffer, user_audio, bot_audio, sample_rate, num_channels + ): + try: + if user_audio: + await in_memory_buffers.user.append(user_audio) + if bot_audio: + await in_memory_buffers.bot.append(bot_audio) + except MemoryError as e: + logger.error(f"Track audio buffer full: {e}") diff --git a/api/services/pipecat/in_memory_buffers.py b/api/services/pipecat/in_memory_buffers.py index 3cf22d55..5c7f3030 100644 --- a/api/services/pipecat/in_memory_buffers.py +++ b/api/services/pipecat/in_memory_buffers.py @@ -75,6 +75,27 @@ class InMemoryAudioBuffer: return self._total_size +class InMemoryRecordingBuffers: + """Holds the mixed recording plus aligned user and bot mono tracks.""" + + def __init__(self, workflow_run_id: int, sample_rate: int, num_channels: int = 1): + self.mixed = InMemoryAudioBuffer( + workflow_run_id=workflow_run_id, + sample_rate=sample_rate, + num_channels=num_channels, + ) + self.user = InMemoryAudioBuffer( + workflow_run_id=workflow_run_id, + sample_rate=sample_rate, + num_channels=1, + ) + self.bot = InMemoryAudioBuffer( + workflow_run_id=workflow_run_id, + sample_rate=sample_rate, + num_channels=1, + ) + + class InMemoryLogsBuffer: """Buffer real-time feedback events in memory during a call, then save to workflow run logs.""" diff --git a/api/services/pipecat/run_pipeline.py b/api/services/pipecat/run_pipeline.py index 07286901..9ee840f8 100644 --- a/api/services/pipecat/run_pipeline.py +++ b/api/services/pipecat/run_pipeline.py @@ -6,6 +6,7 @@ from loguru import logger from api.db import db_client from api.enums import WorkflowRunMode +from api.services.configuration.options import DEEPGRAM_FLUX_MODELS from api.services.configuration.registry import ServiceProviders from api.services.integrations import ( IntegrationRuntimeContext, @@ -469,7 +470,10 @@ async def _run_pipeline( workflow_run_id, initial_context=merged_call_context_vars ) - workflow_graph = WorkflowGraph(ReactFlowDTO.model_validate(run_workflow_json)) + workflow_graph = WorkflowGraph( + ReactFlowDTO.model_validate(run_workflow_json), + skip_instance_constraints_for={"trigger"}, + ) # Pre-call fetch: fire early so it runs concurrently with remaining setup pre_call_fetch_task = None @@ -626,7 +630,7 @@ async def _run_pipeline( # Other models use configurable turn detection strategy is_deepgram_flux = ( user_config.stt.provider == ServiceProviders.DEEPGRAM.value - and user_config.stt.model == "flux-general-en" + and user_config.stt.model in DEEPGRAM_FLUX_MODELS ) if is_deepgram_flux: diff --git a/api/services/pipecat/service_factory.py b/api/services/pipecat/service_factory.py index ec5e9911..b7f64295 100644 --- a/api/services/pipecat/service_factory.py +++ b/api/services/pipecat/service_factory.py @@ -6,6 +6,7 @@ from fastapi import HTTPException from loguru import logger from api.constants import MPS_API_URL +from api.services.configuration.options import DEEPGRAM_FLUX_MODELS from api.services.configuration.registry import ServiceProviders from api.services.pipecat.minimax_tts import MiniMaxOwnedSessionTTSService from api.utils.url_security import validate_user_configured_service_url @@ -39,8 +40,18 @@ from pipecat.services.google.vertex.llm import ( GoogleVertexLLMSettings, ) from pipecat.services.groq.llm import GroqLLMService, GroqLLMSettings +from pipecat.services.huggingface.llm import ( + HuggingFaceLLMService, + HuggingFaceLLMSettings, +) +from pipecat.services.huggingface.stt import ( + HuggingFaceSTTService, + HuggingFaceSTTSettings, +) +from pipecat.services.inworld.tts import InworldTTSService, InworldTTSSettings from pipecat.services.minimax.llm import MiniMaxLLMService from pipecat.services.minimax.tts import MiniMaxTTSSettings +from pipecat.services.openai._constants import OPENAI_SAMPLE_RATE from pipecat.services.openai.base_llm import OpenAILLMSettings from pipecat.services.openai.llm import OpenAILLMService from pipecat.services.openai.stt import ( @@ -53,6 +64,8 @@ from pipecat.services.rime.tts import RimeTTSService, RimeTTSSettings from pipecat.services.sarvam.llm import SarvamLLMService, SarvamLLMSettings from pipecat.services.sarvam.stt import SarvamSTTService, SarvamSTTSettings from pipecat.services.sarvam.tts import SarvamTTSService, SarvamTTSSettings +from pipecat.services.smallest.stt import SmallestSTTService, SmallestSTTSettings +from pipecat.services.smallest.tts import SmallestTTSService, SmallestTTSSettings from pipecat.services.speaches.llm import SpeachesLLMService, SpeachesLLMSettings from pipecat.services.speaches.stt import SpeachesSTTService, SpeachesSTTSettings from pipecat.services.speaches.tts import SpeachesTTSService, SpeachesTTSSettings @@ -67,6 +80,20 @@ if TYPE_CHECKING: from api.services.pipecat.audio_config import AudioConfig +DEEPGRAM_FLUX_LANGUAGE_HINTS = { + "de": Language.DE, + "en": Language.EN, + "es": Language.ES, + "fr": Language.FR, + "hi": Language.HI, + "it": Language.IT, + "ja": Language.JA, + "nl": Language.NL, + "pt": Language.PT, + "ru": Language.RU, +} + + def _validate_runtime_service_url(url: str, field_name: str) -> None: try: validate_user_configured_service_url( @@ -93,17 +120,23 @@ def create_stt_service( f"Creating STT service: provider={user_config.stt.provider}, model={user_config.stt.model}" ) if user_config.stt.provider == ServiceProviders.DEEPGRAM.value: - # Check if using Flux model (English-only, no language selection) - if user_config.stt.model == "flux-general-en": + if user_config.stt.model in DEEPGRAM_FLUX_MODELS: + settings_kwargs = { + "model": user_config.stt.model, + "eot_timeout_ms": 3000, + "eot_threshold": 0.7, + "eager_eot_threshold": 0.5, + "keyterm": keyterms or [], + } + if user_config.stt.model == "flux-general-multi": + language = getattr(user_config.stt, "language", None) + language_hint = DEEPGRAM_FLUX_LANGUAGE_HINTS.get(language) + if language_hint: + settings_kwargs["language_hints"] = [language_hint] + return DeepgramFluxSTTService( api_key=user_config.stt.api_key, - settings=DeepgramFluxSTTSettings( - model=user_config.stt.model, - eot_timeout_ms=3000, - eot_threshold=0.7, - eager_eot_threshold=0.5, - keyterm=keyterms or [], - ), + settings=DeepgramFluxSTTSettings(**settings_kwargs), should_interrupt=False, # Let UserAggregator take care of sending InterruptionFrame sample_rate=audio_config.transport_in_sample_rate, ) @@ -218,6 +251,22 @@ def create_stt_service( ), sample_rate=audio_config.transport_in_sample_rate, ) + elif user_config.stt.provider == ServiceProviders.HUGGINGFACE.value: + base_url = ( + getattr(user_config.stt, "base_url", None) + or "https://router.huggingface.co/hf-inference" + ) + _validate_runtime_service_url(base_url, "base_url") + return HuggingFaceSTTService( + api_key=user_config.stt.api_key, + base_url=base_url, + bill_to=getattr(user_config.stt, "bill_to", None), + settings=HuggingFaceSTTSettings( + model=user_config.stt.model, + return_timestamps=getattr(user_config.stt, "return_timestamps", False), + ), + sample_rate=audio_config.transport_in_sample_rate, + ) elif user_config.stt.provider == ServiceProviders.ASSEMBLYAI.value: language = getattr(user_config.stt, "language", None) settings_kwargs = {"model": user_config.stt.model, "language": language} @@ -284,6 +333,20 @@ def create_stt_service( settings=AzureSTTSettings(language=pipecat_language), sample_rate=audio_config.transport_in_sample_rate, ) + elif user_config.stt.provider == ServiceProviders.SMALLEST.value: + language_code = getattr(user_config.stt, "language", None) or "en" + try: + pipecat_language = Language(language_code) + except ValueError: + pipecat_language = Language.EN + return SmallestSTTService( + api_key=user_config.stt.api_key, + settings=SmallestSTTSettings( + model=user_config.stt.model, + language=pipecat_language, + ), + sample_rate=audio_config.transport_in_sample_rate, + ) else: raise HTTPException( status_code=400, detail=f"Invalid STT provider {user_config.stt.provider}" @@ -320,6 +383,7 @@ def create_tts_service( kwargs["base_url"] = base_url return OpenAITTSService( api_key=user_config.tts.api_key, + sample_rate=OPENAI_SAMPLE_RATE, settings=OpenAITTSSettings(model=user_config.tts.model), text_filters=[xml_function_tag_filter], skip_aggregator_types=["recording_router", "recording"], @@ -389,11 +453,13 @@ def create_tts_service( generation_config = ( GenerationConfig(**gen_config_kwargs) if gen_config_kwargs else None ) + language = getattr(user_config.tts, "language", None) or "en" return CartesiaTTSService( api_key=user_config.tts.api_key, settings=CartesiaTTSSettings( voice=user_config.tts.voice, model=user_config.tts.model, + language=language, **( {"generation_config": generation_config} if generation_config @@ -404,6 +470,25 @@ def create_tts_service( skip_aggregator_types=["recording_router", "recording"], silence_time_s=1.0, ) + elif user_config.tts.provider == ServiceProviders.INWORLD.value: + voice = getattr(user_config.tts, "voice", None) or "Ashley" + model = getattr(user_config.tts, "model", None) or "inworld-tts-2" + speed = getattr(user_config.tts, "speed", None) + language = getattr(user_config.tts, "language", None) or "en-US" + delivery_mode = getattr(user_config.tts, "delivery_mode", None) or "BALANCED" + return InworldTTSService( + api_key=user_config.tts.api_key, + settings=InworldTTSSettings( + voice=voice, + model=model, + language=language, + speaking_rate=speed, + delivery_mode=delivery_mode, + ), + text_filters=[xml_function_tag_filter], + skip_aggregator_types=["recording_router", "recording"], + silence_time_s=1.0, + ) elif user_config.tts.provider == ServiceProviders.DOGRAH.value: # Convert HTTP URL to WebSocket URL for TTS base_url = MPS_API_URL.replace("http://", "ws://").replace("https://", "wss://") @@ -492,14 +577,20 @@ def create_tts_service( language = getattr(user_config.tts, "language", None) pipecat_language = language_mapping.get(language, Language.HI) - voice = getattr(user_config.tts, "voice", None) or "anushka" + voice = ( + getattr(user_config.tts, "voice", None) or "" + ).strip().lower() or "anushka" + speed = getattr(user_config.tts, "speed", None) + settings_kwargs = { + "model": user_config.tts.model, + "voice": voice, + "language": pipecat_language, + } + if speed and speed != 1.0: + settings_kwargs["pace"] = speed return SarvamTTSService( api_key=user_config.tts.api_key, - settings=SarvamTTSSettings( - model=user_config.tts.model, - voice=voice, - language=pipecat_language, - ), + settings=SarvamTTSSettings(**settings_kwargs), text_filters=[xml_function_tag_filter], skip_aggregator_types=["recording_router", "recording"], silence_time_s=1.0, @@ -560,6 +651,28 @@ def create_tts_service( skip_aggregator_types=["recording_router", "recording"], silence_time_s=1.0, ) + elif user_config.tts.provider == ServiceProviders.SMALLEST.value: + language_code = getattr(user_config.tts, "language", None) or "en" + try: + pipecat_language = Language(language_code) + except ValueError: + pipecat_language = Language.EN + speed = getattr(user_config.tts, "speed", None) + model = user_config.tts.model.replace("lightning-v", "lightning_v") + settings_kwargs = SmallestTTSSettings( + model=model, + voice=user_config.tts.voice, + language=pipecat_language, + ) + if speed and speed != 1.0: + settings_kwargs.speed = speed + return SmallestTTSService( + api_key=user_config.tts.api_key, + settings=settings_kwargs, + text_filters=[xml_function_tag_filter], + skip_aggregator_types=["recording_router", "recording"], + silence_time_s=1.0, + ) else: raise HTTPException( status_code=400, detail=f"Invalid TTS provider {user_config.tts.provider}" @@ -581,6 +694,7 @@ def create_llm_service_from_provider( location: str | None = None, credentials: str | None = None, temperature: float | None = None, + bill_to: str | None = None, ): """Create an LLM service from explicit provider/model/api_key. @@ -663,6 +777,15 @@ def create_llm_service_from_provider( api_key=api_key or "none", settings=SpeachesLLMSettings(model=model), ) + elif provider == ServiceProviders.HUGGINGFACE.value: + base_url = base_url or "https://router.huggingface.co/v1" + _validate_runtime_service_url(base_url, "base_url") + return HuggingFaceLLMService( + api_key=api_key, + base_url=base_url, + bill_to=bill_to, + settings=HuggingFaceLLMSettings(model=model, temperature=0.1), + ) elif provider == ServiceProviders.MINIMAX.value: base_url = base_url or "https://api.minimax.io/v1" _validate_runtime_service_url(base_url, "base_url") @@ -875,6 +998,9 @@ def create_llm_service(user_config, correlation_id: str | None = None): kwargs["endpoint"] = user_config.llm.endpoint elif provider == ServiceProviders.SPEACHES.value: kwargs["base_url"] = user_config.llm.base_url + elif provider == ServiceProviders.HUGGINGFACE.value: + kwargs["base_url"] = user_config.llm.base_url + kwargs["bill_to"] = user_config.llm.bill_to elif provider == ServiceProviders.AWS_BEDROCK.value: kwargs["aws_access_key"] = user_config.llm.aws_access_key kwargs["aws_secret_key"] = user_config.llm.aws_secret_key diff --git a/api/services/posthog_client.py b/api/services/posthog_client.py index 1b4d5e03..15e3a4ac 100644 --- a/api/services/posthog_client.py +++ b/api/services/posthog_client.py @@ -1,31 +1,95 @@ +from typing import Any, Optional + from loguru import logger from posthog import Posthog -from api.constants import ENABLE_TELEMETRY, POSTHOG_API_KEY, POSTHOG_HOST +from api.constants import POSTHOG_API_KEY, POSTHOG_HOST _posthog_client: Posthog | None = None +POSTHOG_SERVER_GROUP_IDENTIFY_DISTINCT_ID = "server-group-identify" def get_posthog() -> Posthog | None: """Return the lazily-initialised PostHog client, or None if not configured.""" global _posthog_client - if _posthog_client is None and POSTHOG_API_KEY and ENABLE_TELEMETRY: + if _posthog_client is None and POSTHOG_API_KEY: _posthog_client = Posthog(POSTHOG_API_KEY, host=POSTHOG_HOST) return _posthog_client +def shutdown_posthog() -> None: + """Flush queued PostHog messages before a short-lived process exits.""" + client = get_posthog() + if not client: + return + try: + client.shutdown() + except Exception: + logger.exception("Failed to shut down PostHog client") + + +def flush_posthog() -> None: + """Flush queued PostHog messages without shutting down the client.""" + client = get_posthog() + if not client: + return + try: + client.flush() + except Exception: + logger.exception("Failed to flush PostHog client") + + def capture_event( distinct_id: str, event: str, - properties: dict | None = None, + properties: dict[str, Any] | None = None, + groups: Optional[dict[str, str]] = None, ) -> None: """Fire a PostHog event. Silently no-ops if PostHog is not configured.""" client = get_posthog() if not client: return try: - client.capture( - distinct_id=distinct_id, event=event, properties=properties or {} - ) + kwargs: dict[str, Any] = { + "distinct_id": distinct_id, + "event": event, + "properties": properties or {}, + } + if groups: + kwargs["groups"] = groups + client.capture(**kwargs) except Exception: logger.exception(f"Failed to send PostHog event '{event}'") + + +def group_identify( + group_type: str, + group_key: str, + properties: dict[str, Any], + *, + distinct_id: Optional[str] = None, +) -> None: + """Set PostHog group properties. Silently no-ops if PostHog is not configured.""" + client = get_posthog() + if not client: + return + try: + client.group_identify( + group_type, + group_key, + properties, + distinct_id=distinct_id or POSTHOG_SERVER_GROUP_IDENTIFY_DISTINCT_ID, + ) + except Exception: + logger.exception("Failed to identify PostHog group") + + +def set_person_properties(distinct_id: str, properties: dict[str, Any]) -> None: + """Set PostHog person properties. Silently no-ops if PostHog is not configured.""" + client = get_posthog() + if not client: + return + try: + client.set(distinct_id=distinct_id, properties=properties) + except Exception: + logger.exception("Failed to set PostHog person properties") diff --git a/api/services/quota_service.py b/api/services/quota_service.py index 6633736e..9dd8528f 100644 --- a/api/services/quota_service.py +++ b/api/services/quota_service.py @@ -37,6 +37,12 @@ BILLING_V2_QUOTA_EXCEEDED_MESSAGE = ( "or change providers in Models configurations." ) +SERVICE_TOKEN_ORG_MISMATCH_MESSAGE = ( + "The Dograh service token being used is created from another account. " + "Please create a new service token from the Developers tab and use it in " + "your model configuration." +) + @dataclass class QuotaCheckResult: @@ -98,6 +104,26 @@ def _dograh_api_keys(user_config: Any) -> set[str]: return api_keys +def _is_service_key_org_mismatch_error(error: Exception) -> bool: + response = getattr(error, "response", None) + if getattr(response, "status_code", None) != 403: + return False + + detail: Any = None + try: + payload = response.json() + if isinstance(payload, dict): + detail = payload.get("detail") + except Exception: + detail = None + + if isinstance(detail, str): + return detail.lower() == "service key organization mismatch" + + response_text = getattr(response, "text", "") + return "Service key organization mismatch" in response_text + + async def _store_run_correlation_id( workflow_run_id: int | None, correlation_id: str | None, @@ -173,11 +199,20 @@ async def _authorize_hosted_workflow_run_start( }, ) except Exception as e: - logger.error( + logger.warning( "Failed to authorize workflow start with MPS for org {}: {}", organization_id, e, ) + if _is_service_key_org_mismatch_error(e): + return ( + QuotaCheckResult( + has_quota=False, + error_code="service_key_org_mismatch", + error_message=SERVICE_TOKEN_ORG_MISMATCH_MESSAGE, + ), + True, + ) return ( QuotaCheckResult( has_quota=False, diff --git a/api/services/user_onboarding.py b/api/services/user_onboarding.py new file mode 100644 index 00000000..273e607f --- /dev/null +++ b/api/services/user_onboarding.py @@ -0,0 +1,37 @@ +from loguru import logger +from pydantic import ValidationError + +from api.db import db_client +from api.enums import UserConfigurationKey +from api.schemas.onboarding_state import OnboardingState, OnboardingStateUpdate + + +async def get_onboarding_state(user_id: int) -> OnboardingState: + value = await db_client.get_user_configuration_value( + user_id, UserConfigurationKey.ONBOARDING.value + ) + return _parse_state(value, user_id) + + +async def update_onboarding_state( + user_id: int, update: OnboardingStateUpdate +) -> OnboardingState: + state = update.apply_to(await get_onboarding_state(user_id)) + await db_client.upsert_user_configuration_value( + user_id, + UserConfigurationKey.ONBOARDING.value, + state.model_dump(mode="json", exclude_none=True), + ) + return state + + +def _parse_state(value, user_id: int) -> OnboardingState: + if not value or not isinstance(value, dict): + return OnboardingState() + try: + return OnboardingState.model_validate(value) + except ValidationError as exc: + logger.warning( + f"Invalid onboarding state for user {user_id}: {exc}. Returning defaults." + ) + return OnboardingState() diff --git a/api/services/workflow/audit.py b/api/services/workflow/audit.py index 8e703847..46e22ad5 100644 --- a/api/services/workflow/audit.py +++ b/api/services/workflow/audit.py @@ -7,15 +7,19 @@ script in `api/services/admin_utils/local_exec.py` is the production consumer. """ +from collections import Counter + from api.services.workflow.node_specs import all_specs -def _build_type_rules() -> tuple[set[str], set[str]]: +def _build_type_rules() -> tuple[set[str], set[str], dict[str, int], dict[str, int]]: """From NodeSpec.graph_constraints, derive the set of types that are forbidden as edge sources (max_outgoing == 0) and as targets (max_incoming == 0).""" src_forbidden: set[str] = set() tgt_forbidden: set[str] = set() + min_instances: dict[str, int] = {} + max_instances: dict[str, int] = {} for spec in all_specs(): gc = spec.graph_constraints if gc is None: @@ -24,7 +28,11 @@ def _build_type_rules() -> tuple[set[str], set[str]]: src_forbidden.add(spec.name) if gc.max_incoming == 0: tgt_forbidden.add(spec.name) - return src_forbidden, tgt_forbidden + if gc.min_instances is not None: + min_instances[spec.name] = gc.min_instances + if gc.max_instances is not None: + max_instances[spec.name] = gc.max_instances + return src_forbidden, tgt_forbidden, min_instances, max_instances def _empty_violation(reason: str) -> dict: @@ -49,7 +57,7 @@ def audit_definition(nodes, edges) -> list[dict]: if not isinstance(nodes, list) or not isinstance(edges, list): return [] - src_forbidden, tgt_forbidden = _build_type_rules() + src_forbidden, tgt_forbidden, min_instances, max_instances = _build_type_rules() nodes_by_id: dict = {} for n in nodes: if isinstance(n, dict) and "id" in n: @@ -57,14 +65,25 @@ def audit_definition(nodes, edges) -> list[dict]: violations: list[dict] = [] - # Graph-level: WorkflowGraph._assert_start_node requires exactly one - # startCall node. The DTO doesn't enforce this, so legacy or - # script-edited rows can land in a state that fails at runtime. - start_count = sum(1 for t in nodes_by_id.values() if t == "startCall") - if start_count == 0: - violations.append(_empty_violation("no_start_node")) - elif start_count > 1: - violations.append(_empty_violation(f"multiple_start_nodes:{start_count}")) + node_counts = Counter(t for t in nodes_by_id.values() if isinstance(t, str)) + for node_type, min_count in min_instances.items(): + count = node_counts.get(node_type, 0) + if count < min_count: + reason = ( + "no_start_node" + if node_type == "startCall" and min_count == 1 + else f"min_instances_{min_count}:{node_type}:{count}" + ) + violations.append(_empty_violation(reason)) + for node_type, max_count in max_instances.items(): + count = node_counts.get(node_type, 0) + if count > max_count: + reason = ( + f"multiple_start_nodes:{count}" + if node_type == "startCall" and max_count == 1 + else f"max_instances_{max_count}:{node_type}:{count}" + ) + violations.append(_empty_violation(reason)) for e in edges: if not isinstance(e, dict): continue diff --git a/api/services/workflow/dto.py b/api/services/workflow/dto.py index 60aad758..6bb5f9c9 100644 --- a/api/services/workflow/dto.py +++ b/api/services/workflow/dto.py @@ -196,7 +196,12 @@ class _ToolDocumentRefsMixin(BaseModel): }, ) ], - graph_constraints=GraphConstraints(min_incoming=0, max_incoming=0), + graph_constraints=GraphConstraints( + min_incoming=0, + max_incoming=0, + min_instances=1, + max_instances=1, + ), property_order=( "name", "greeting_type", @@ -539,6 +544,7 @@ class EndCallNodeData( max_incoming=0, min_outgoing=0, max_outgoing=0, + max_instances=1, ), property_order=("name", "prompt"), field_overrides={ @@ -597,7 +603,11 @@ class GlobalNodeData(BaseNodeData, _PromptedNodeDataMixin): examples=[ NodeExample(name="default", data={"name": "Inbound Trigger", "enabled": True}) ], - graph_constraints=GraphConstraints(min_incoming=0, max_incoming=0), + graph_constraints=GraphConstraints( + min_incoming=0, + max_incoming=0, + max_instances=1, + ), property_order=("name", "enabled", "trigger_path"), field_overrides={ "name": { @@ -718,6 +728,8 @@ class TriggerNodeData(BaseNodeData): "rsvp": "{{gathered_context.rsvp}}", "duration": "{{cost_info.call_duration_seconds}}", "recording_url": "{{recording_url}}", + "user_recording_url": "{{user_recording_url}}", + "bot_recording_url": "{{bot_recording_url}}", "transcript_url": "{{transcript_url}}", }, }, diff --git a/api/services/workflow/node_specs/_base.py b/api/services/workflow/node_specs/_base.py index b8324c10..bb864298 100644 --- a/api/services/workflow/node_specs/_base.py +++ b/api/services/workflow/node_specs/_base.py @@ -243,6 +243,8 @@ class GraphConstraints(BaseModel): max_incoming: Optional[int] = None min_outgoing: Optional[int] = None max_outgoing: Optional[int] = None + min_instances: Optional[int] = None + max_instances: Optional[int] = None model_config = ConfigDict(extra="forbid") diff --git a/api/services/workflow/qa/analysis.py b/api/services/workflow/qa/analysis.py index 0afb2e19..9c378bca 100644 --- a/api/services/workflow/qa/analysis.py +++ b/api/services/workflow/qa/analysis.py @@ -8,6 +8,7 @@ from pipecat.processors.aggregators.llm_context import LLMContext from api.db.models import WorkflowRunModel from api.services.gen_ai.json_parser import parse_llm_json +from api.services.managed_model_services import get_mps_correlation_id from api.services.pipecat.service_factory import create_llm_service_from_provider from api.services.workflow.dto import QANodeData from api.services.workflow.qa.conversation import ( @@ -132,8 +133,15 @@ async def run_per_node_qa_analysis( # Set up Langfuse tracing parent_ctx = setup_langfuse_parent_context(workflow_run) - # Build LLM service - llm = create_llm_service_from_provider(provider, model, api_key, **service_kwargs) + # Build LLM service. Reuse the run's MPS correlation id (minted at run + # start, persisted on initial_context) so managed-model-services calls carry + # billing-v2 markers — orgs on billing v2 reject managed calls that lack them. + mps_correlation_id = get_mps_correlation_id( + getattr(workflow_run, "initial_context", None) + ) + llm = create_llm_service_from_provider( + provider, model, api_key, correlation_id=mps_correlation_id, **service_kwargs + ) node_results: dict[str, Any] = {} prior_conversation: list[dict] = [] # Running accumulation of all prior nodes @@ -206,6 +214,16 @@ async def run_per_node_qa_analysis( } try: parsed = parse_llm_json(raw_response) + # parse_llm_json can return a list (e.g. when the model emits a + # top-level JSON array); coerce non-dict results so the .get() + # lookups below don't raise AttributeError. + if not isinstance(parsed, dict): + logger.warning( + f"QA LLM returned non-object JSON for node '{node_name}' " + f"on run {workflow_run_id}; got {type(parsed).__name__}, " + "using empty QA result" + ) + parsed = {} node_result["tags"] = parsed.get("tags", []) node_result["summary"] = parsed.get("summary", "") node_result["score"] = parsed.get("call_quality_score") @@ -280,8 +298,14 @@ async def _run_whole_call_qa_analysis( {"role": "user", "content": f"## Transcript\n{transcript}"}, ] - # Call LLM - llm = create_llm_service_from_provider(provider, model, api_key, **service_kwargs) + # Build LLM service. Reuse the run's MPS correlation id so managed-model + # calls carry billing-v2 markers (see run_per_node_qa_analysis). + mps_correlation_id = get_mps_correlation_id( + getattr(workflow_run, "initial_context", None) + ) + llm = create_llm_service_from_provider( + provider, model, api_key, correlation_id=mps_correlation_id, **service_kwargs + ) try: raw_response = await _run_llm_inference(llm, messages, system_content) @@ -296,6 +320,16 @@ async def _run_whole_call_qa_analysis( } try: parsed = parse_llm_json(raw_response) + # parse_llm_json can return a list (e.g. when the model emits a + # top-level JSON array); coerce non-dict results so the .get() + # lookups below don't raise AttributeError. + if not isinstance(parsed, dict): + logger.warning( + f"QA LLM returned non-object JSON for whole-call QA on run " + f"{workflow_run_id}; got {type(parsed).__name__}, using empty " + "QA result" + ) + parsed = {} node_result["tags"] = parsed.get("tags", []) node_result["summary"] = parsed.get("summary", "") node_result["score"] = parsed.get("call_quality_score") diff --git a/api/services/workflow/text_chat_runner.py b/api/services/workflow/text_chat_runner.py index 6cc4615a..b9fa06ab 100644 --- a/api/services/workflow/text_chat_runner.py +++ b/api/services/workflow/text_chat_runner.py @@ -17,7 +17,6 @@ from pipecat.frames.frames import ( LLMContextFrame, LLMFullResponseEndFrame, LLMFullResponseStartFrame, - TextFrame, TTSSpeakFrame, TTSStoppedFrame, ) @@ -167,12 +166,17 @@ class _TaskQueueProxy: class _TextChatCaptureProcessor(FrameProcessor): - def __init__(self, response_window: _ResponseWindowState) -> None: + def __init__( + self, + response_window: _ResponseWindowState, + context: LLMContext, + ) -> None: super().__init__() self.last_activity_at = time.monotonic() self.activity_count = 0 self.events: list[dict[str, Any]] = [] self._response_window = response_window + self._context = context def _touch(self) -> None: self.last_activity_at = time.monotonic() @@ -192,12 +196,14 @@ class _TextChatCaptureProcessor(FrameProcessor): self._touch() if isinstance(frame, TTSSpeakFrame): - text_frame = TextFrame(frame.text) - text_frame.append_to_context = ( + append_to_context = ( frame.append_to_context if frame.append_to_context is not None else True ) - await self.push_frame(text_frame, direction) - await self.push_frame(LLMAssistantPushAggregationFrame(), direction) + text = frame.text.strip() + if text: + self._response_window.outputs.append(text) + if append_to_context: + self._context.add_message({"role": "assistant", "content": text}) return if isinstance(frame, LLMContextFrame) and direction == FrameDirection.UPSTREAM: @@ -452,14 +458,15 @@ async def execute_text_chat_pending_turn( ) workflow_graph = WorkflowGraph( - ReactFlowDTO.model_validate(run_definition.workflow_json) + ReactFlowDTO.model_validate(run_definition.workflow_json), + skip_instance_constraints_for={"trigger"}, ) base_checkpoint = _resolve_checkpoint_for_pending_turn(session_data, checkpoint) - response_window = _ResponseWindowState() - capture_processor = _TextChatCaptureProcessor(response_window) context = LLMContext() context.set_messages(base_checkpoint["messages"]) + response_window = _ResponseWindowState() + capture_processor = _TextChatCaptureProcessor(response_window, context) node_transition_events = capture_processor.events diff --git a/api/services/workflow/text_chat_session_service.py b/api/services/workflow/text_chat_session_service.py index 81749960..9f4ec4d4 100644 --- a/api/services/workflow/text_chat_session_service.py +++ b/api/services/workflow/text_chat_session_service.py @@ -201,7 +201,7 @@ async def execute_pending_text_chat_turn( error_message=str(e), ) raise TextChatSessionExecutionError( - "Failed to execute text chat assistant turn" + f"Failed to execute text chat assistant turn: {e}" ) from e completed_session_data = normalize_text_chat_session_data(text_session.session_data) diff --git a/api/services/workflow/trigger_paths.py b/api/services/workflow/trigger_paths.py index ed34345b..f53f6934 100644 --- a/api/services/workflow/trigger_paths.py +++ b/api/services/workflow/trigger_paths.py @@ -127,8 +127,7 @@ def validate_trigger_paths( ) ) - first_node_id = seen_paths.get(trigger_path) - if first_node_id is None: + if trigger_path not in seen_paths: seen_paths[trigger_path] = node_id else: issues.append( diff --git a/api/services/workflow/workflow_graph.py b/api/services/workflow/workflow_graph.py index a6268159..6f9623fb 100644 --- a/api/services/workflow/workflow_graph.py +++ b/api/services/workflow/workflow_graph.py @@ -5,7 +5,7 @@ from typing import 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_data import BaseNodeData -from api.services.workflow.node_specs import get_spec +from api.services.workflow.node_specs import all_specs, get_spec # Regex for matching {{ variable }} template placeholders. # Captures: group(1) = variable path, group(2) = filter name, group(3) = filter value. @@ -68,10 +68,11 @@ class Node: self.out: Dict[str, "Node"] = {} # forward nodes self.out_edges: List[Edge] = [] # forward edges with properties - # name/is_start/is_end live on every per-type data class (base). + # Start/end semantics are defined by node type. The persisted + # data flags are legacy UI/runtime state and may be stale. self.name = data.name - self.is_start = data.is_start - self.is_end = data.is_end + self.is_start = node_type == NodeType.startNode.value + self.is_end = node_type == NodeType.endNode.value # Type-specific fields — read with getattr so this works for every # node variant in the discriminated union. @@ -98,13 +99,89 @@ class Node: self.data = data +def _instance_constraint_message( + label: str, + count: int, + *, + min_count: int | None = None, + max_count: int | None = None, +) -> str: + if max_count is not None and count > max_count: + if max_count == 1: + return f"Workflow can have at most one {label}" + return f"Workflow can have at most {max_count} {label} nodes" + if min_count is not None and count < min_count: + if min_count == 1: + return f"Workflow must have at least one {label}" + return f"Workflow must have at least {min_count} {label} nodes" + return "" + + +def validate_node_instance_constraints( + node_types: list[str], + *, + enforce_min_instances: bool = True, + skip_types: Set[str] | None = None, +) -> list[WorkflowError]: + """Validate workflow-level node type counts from NodeSpec.graph_constraints.""" + errors: list[WorkflowError] = [] + skip_types = skip_types or set() + counts = Counter(node_types) + + for spec in all_specs(): + if spec.name in skip_types: + continue + gc = spec.graph_constraints + if gc is None: + continue + + count = counts.get(spec.name, 0) + if gc.max_instances is not None and count > gc.max_instances: + errors.append( + WorkflowError( + kind=ItemKind.workflow, + id=None, + field=None, + message=_instance_constraint_message( + spec.display_name, + count, + max_count=gc.max_instances, + ), + ) + ) + if ( + enforce_min_instances + and gc.min_instances is not None + and count < gc.min_instances + ): + errors.append( + WorkflowError( + kind=ItemKind.workflow, + id=None, + field=None, + message=_instance_constraint_message( + spec.display_name, + count, + min_count=gc.min_instances, + ), + ) + ) + + return errors + + class WorkflowGraph: """ *All* business invariants (acyclic, cardinality, etc.) are verified here. The constructor accepts a validated ReactFlowDTO. """ - def __init__(self, dto: ReactFlowDTO): + def __init__( + self, + dto: ReactFlowDTO, + *, + skip_instance_constraints_for: Set[str] | None = None, + ): # 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. @@ -131,10 +208,12 @@ class WorkflowGraph: # Set up the node references for backward compatibility source_node.out[target_node.id] = target_node - self._validate_graph() + self._validate_graph(skip_instance_constraints_for or set()) # Get a reference to the start node - self.start_node_id = [n.id for n in dto.nodes if n.data.is_start][0] + self.start_node_id = [ + n.id for n in dto.nodes if n.type == NodeType.startNode.value + ][0] # Get a reference to the global node try: @@ -185,7 +264,7 @@ class WorkflowGraph: # ----------------------------------------------------------- # validators # ----------------------------------------------------------- - def _validate_graph(self) -> None: + def _validate_graph(self, skip_instance_constraints_for: Set[str]) -> None: errors: list[WorkflowError] = [] # TODO: Figure out what kind of cyclic contraints can be applied, since there can be a cycle in the graph @@ -198,9 +277,13 @@ class WorkflowGraph: # ) # ) - errors.extend(self._assert_start_node()) + errors.extend( + validate_node_instance_constraints( + [n.node_type for n in self.nodes.values()], + skip_types=skip_instance_constraints_for, + ) + ) errors.extend(self._assert_connection_counts()) - errors.extend(self._assert_global_node()) errors.extend(self._assert_node_configs()) if errors: raise ValueError(errors) @@ -220,48 +303,6 @@ class WorkflowGraph: for n in self.nodes.values(): dfs(n) - def _assert_start_node(self): - errors: list[WorkflowError] = [] - start_nodes = [n for n in self.nodes.values() if n.data.is_start] - if not start_nodes: - errors.append( - WorkflowError( - kind=ItemKind.workflow, - id=None, - field=None, - message="Workflow has no start node — exactly one is required", - ) - ) - elif len(start_nodes) > 1: - errors.append( - WorkflowError( - kind=ItemKind.workflow, - id=None, - field=None, - message=( - f"Workflow has {len(start_nodes)} start nodes — " - f"exactly one is required" - ), - ) - ) - return errors - - def _assert_global_node(self): - errors: list[WorkflowError] = [] - global_node = [ - n for n in self.nodes.values() if n.node_type == NodeType.globalNode.value - ] - if not len(global_node) <= 1: - errors.append( - WorkflowError( - kind=ItemKind.workflow, - id=None, - field=None, - message="Workflow must have at most one global node", - ) - ) - return errors - def _assert_connection_counts(self): """Enforce per-type incoming/outgoing edge constraints. diff --git a/api/services/workflow_run_billing.py b/api/services/workflow_run_billing.py index ab8a3121..18ef328d 100644 --- a/api/services/workflow_run_billing.py +++ b/api/services/workflow_run_billing.py @@ -39,12 +39,22 @@ async def _organization_uses_mps_billing_v2(organization_id: int) -> bool: return bool(account and account.get("billing_mode") == "v2") +def _is_usage_not_ready_error(exc: Exception) -> bool: + response = getattr(exc, "response", None) + if getattr(response, "status_code", None) != 409: + return False + return "usage_not_ready" in (getattr(response, "text", "") or "") + + async def report_workflow_run_platform_usage(workflow_run) -> None: """Report hosted platform usage for a completed workflow run to MPS.""" if DEPLOYMENT_MODE == "oss": return if not getattr(workflow_run, "is_completed", False): + logger.warning( + "Workflow run is not completed in report_workflow_run_platform_usage" + ) return organization_id = _workflow_run_organization_id(workflow_run) @@ -70,6 +80,9 @@ async def report_workflow_run_platform_usage(workflow_run) -> None: try: if not await _organization_uses_mps_billing_v2(organization_id): + logger.debug( + "Not reporting platform usage since org not using mps billing v2" + ) return result = await mps_service_key_client.report_platform_usage( @@ -91,11 +104,21 @@ async def report_workflow_run_platform_usage(workflow_run) -> None: result, ) except Exception as e: - logger.error( - "Failed to report platform usage for workflow run {}: {}", - workflow_run.id, - e, - ) + if _is_usage_not_ready_error(e): + # A run can start and receive an MPS correlation id, then fail or end + # before billable STT usage is recorded. MPS returns usage_not_ready + # for that no-platform-fee path, so keep it out of error alerts. + logger.warning( + "Failed to report platform usage for workflow run {}: {}", + workflow_run.id, + e, + ) + else: + logger.error( + "Failed to report platform usage for workflow run {}: {}", + workflow_run.id, + e, + ) async def report_completed_workflow_run_platform_usage(workflow_run_id: int) -> None: diff --git a/api/tasks/run_integrations.py b/api/tasks/run_integrations.py index be11a3ce..d99eaf0e 100644 --- a/api/tasks/run_integrations.py +++ b/api/tasks/run_integrations.py @@ -27,6 +27,7 @@ from api.services.workflow.dto import ( ) from api.services.workflow.qa import run_per_node_qa_analysis from api.utils.credential_auth import build_auth_header +from api.utils.recording_artifacts import get_recording_storage_key from api.utils.template_renderer import render_template @@ -339,6 +340,10 @@ def _build_render_context( Returns: Dict containing all fields available for template rendering """ + extra = workflow_run.extra or {} + user_recording_key = get_recording_storage_key(extra, "user") + bot_recording_key = get_recording_storage_key(extra, "bot") + context = { # Top-level fields "workflow_run_id": workflow_run.id, @@ -353,6 +358,7 @@ def _build_render_context( "cost_info": workflow_run.usage_info or {}, # Annotations (includes QA results) "annotations": workflow_run.annotations or {}, + "extra": extra, } # Add public download URLs if token is available @@ -366,9 +372,17 @@ def _build_render_context( context["transcript_url"] = ( f"{base_url}/transcript" if workflow_run.transcript_url else None ) + context["user_recording_url"] = ( + f"{base_url}/user_recording" if user_recording_key else None + ) + context["bot_recording_url"] = ( + f"{base_url}/bot_recording" if bot_recording_key else None + ) else: context["recording_url"] = workflow_run.recording_url context["transcript_url"] = workflow_run.transcript_url + context["user_recording_url"] = user_recording_key + context["bot_recording_url"] = bot_recording_key return context diff --git a/api/tasks/workflow_completion.py b/api/tasks/workflow_completion.py index ff0482d2..6943c0d6 100644 --- a/api/tasks/workflow_completion.py +++ b/api/tasks/workflow_completion.py @@ -12,11 +12,51 @@ from api.services.workflow_run_billing import ( from api.tasks.run_integrations import run_integrations_post_workflow_run +def _recording_metadata(storage_key: str, storage_backend: str, track: str) -> dict: + return { + "storage_key": storage_key, + "storage_backend": storage_backend, + "format": "wav", + "track": track, + } + + +async def _upload_temp_file( + workflow_run_id: int, + temp_file_path: str, + storage_key: str, + label: str, +) -> bool: + try: + if not os.path.exists(temp_file_path): + logger.warning(f"{label} temp file not found: {temp_file_path}") + return False + + file_size = os.path.getsize(temp_file_path) + logger.debug(f"{label} file size: {file_size} bytes") + + await storage_fs.aupload_file(temp_file_path, storage_key) + logger.info(f"Successfully uploaded {label}: {storage_key}") + return True + except Exception as e: + logger.error(f"Error uploading {label} for workflow {workflow_run_id}: {e}") + return False + finally: + if os.path.exists(temp_file_path): + try: + os.remove(temp_file_path) + logger.debug(f"Cleaned up temp {label} file: {temp_file_path}") + except Exception as e: + logger.warning(f"Failed to clean up temp {label} file: {e}") + + async def process_workflow_completion( _ctx, workflow_run_id: int, audio_temp_path: Optional[str] = None, transcript_temp_path: Optional[str] = None, + user_audio_temp_path: Optional[str] = None, + bot_audio_temp_path: Optional[str] = None, ): """Process workflow completion: upload artifacts and run integrations. @@ -28,6 +68,8 @@ async def process_workflow_completion( workflow_run_id: The workflow run ID audio_temp_path: Optional path to temp audio file transcript_temp_path: Optional path to temp transcript file + user_audio_temp_path: Optional path to temp user-track audio file + bot_audio_temp_path: Optional path to temp bot-track audio file """ run_id = str(workflow_run_id) set_current_run_id(run_id) @@ -37,35 +79,55 @@ async def process_workflow_completion( storage_backend = get_current_storage_backend() # Step 1: Upload audio if provided + recordings_metadata: dict[str, dict] = {} + if audio_temp_path: - try: - if os.path.exists(audio_temp_path): - file_size = os.path.getsize(audio_temp_path) - logger.debug(f"Audio file size: {file_size} bytes") + recording_url = f"recordings/{workflow_run_id}.wav" + logger.info( + f"Uploading mixed audio to {storage_backend.name} - workflow_run_id: {workflow_run_id}" + ) + if await _upload_temp_file( + workflow_run_id, audio_temp_path, recording_url, "mixed audio" + ): + recordings_metadata["mixed"] = _recording_metadata( + recording_url, storage_backend.value, "mixed" + ) + await db_client.update_workflow_run( + run_id=workflow_run_id, + recording_url=recording_url, + storage_backend=storage_backend.value, + ) - recording_url = f"recordings/{workflow_run_id}.wav" - logger.info( - f"Uploading audio to {storage_backend.name} - workflow_run_id: {workflow_run_id}" - ) + if user_audio_temp_path: + user_recording_url = f"recordings/{workflow_run_id}/user.wav" + logger.info( + f"Uploading user audio to {storage_backend.name} - workflow_run_id: {workflow_run_id}" + ) + if await _upload_temp_file( + workflow_run_id, user_audio_temp_path, user_recording_url, "user audio" + ): + recordings_metadata["user"] = _recording_metadata( + user_recording_url, storage_backend.value, "user" + ) - await storage_fs.aupload_file(audio_temp_path, recording_url) - await db_client.update_workflow_run( - run_id=workflow_run_id, - recording_url=recording_url, - storage_backend=storage_backend.value, - ) - logger.info(f"Successfully uploaded audio: {recording_url}") - else: - logger.warning(f"Audio temp file not found: {audio_temp_path}") - except Exception as e: - logger.error(f"Error uploading audio for workflow {workflow_run_id}: {e}") - finally: - if audio_temp_path and os.path.exists(audio_temp_path): - try: - os.remove(audio_temp_path) - logger.debug(f"Cleaned up temp audio file: {audio_temp_path}") - except Exception as e: - logger.warning(f"Failed to clean up temp audio file: {e}") + if bot_audio_temp_path: + bot_recording_url = f"recordings/{workflow_run_id}/bot.wav" + logger.info( + f"Uploading bot audio to {storage_backend.name} - workflow_run_id: {workflow_run_id}" + ) + if await _upload_temp_file( + workflow_run_id, bot_audio_temp_path, bot_recording_url, "bot audio" + ): + recordings_metadata["bot"] = _recording_metadata( + bot_recording_url, storage_backend.value, "bot" + ) + + if recordings_metadata: + await db_client.update_workflow_run( + run_id=workflow_run_id, + storage_backend=storage_backend.value, + extra={"recordings": recordings_metadata}, + ) # Step 2: Upload transcript if provided if transcript_temp_path: diff --git a/api/tests/dto_fixtures/multiple_global_nodes.json b/api/tests/dto_fixtures/multiple_global_nodes.json new file mode 100644 index 00000000..c164bcd0 --- /dev/null +++ b/api/tests/dto_fixtures/multiple_global_nodes.json @@ -0,0 +1,23 @@ +{ + "nodes": [ + { + "id": "s1", + "type": "startCall", + "position": {"x": 0, "y": 0}, + "data": {"name": "Start", "prompt": "Greet.", "is_start": true} + }, + { + "id": "g1", + "type": "globalNode", + "position": {"x": 0, "y": 200}, + "data": {"name": "Global A", "prompt": "Use a calm tone."} + }, + { + "id": "g2", + "type": "globalNode", + "position": {"x": 0, "y": 400}, + "data": {"name": "Global B", "prompt": "Keep answers short."} + } + ], + "edges": [] +} diff --git a/api/tests/dto_fixtures/multiple_trigger_nodes.json b/api/tests/dto_fixtures/multiple_trigger_nodes.json new file mode 100644 index 00000000..02e17ff6 --- /dev/null +++ b/api/tests/dto_fixtures/multiple_trigger_nodes.json @@ -0,0 +1,23 @@ +{ + "nodes": [ + { + "id": "s1", + "type": "startCall", + "position": {"x": 0, "y": 0}, + "data": {"name": "Start", "prompt": "Greet.", "is_start": true} + }, + { + "id": "t1", + "type": "trigger", + "position": {"x": 0, "y": 200}, + "data": {"name": "Trigger A", "trigger_path": "trigger_a"} + }, + { + "id": "t2", + "type": "trigger", + "position": {"x": 0, "y": 400}, + "data": {"name": "Trigger B", "trigger_path": "trigger_b"} + } + ], + "edges": [] +} diff --git a/api/tests/test_ai_model_configuration_v2.py b/api/tests/test_ai_model_configuration_v2.py index 57f7cf83..86faa549 100644 --- a/api/tests/test_ai_model_configuration_v2.py +++ b/api/tests/test_ai_model_configuration_v2.py @@ -1,5 +1,5 @@ from types import SimpleNamespace -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, Mock import pytest from pydantic import ValidationError @@ -59,13 +59,27 @@ def test_dograh_v2_compiles_to_effective_managed_pipeline_with_embeddings(): assert effective.managed_service_version == 2 -def test_dograh_v2_rejects_non_predefined_speed(): +def test_dograh_v2_accepts_numeric_speed_in_registry_range(): + config = OrganizationAIModelConfigurationV2( + mode="dograh", + dograh=DograhManagedAIModelConfiguration( + api_key="mps-secret", + speed=1.5, + ), + ) + + effective = compile_ai_model_configuration_v2(config) + + assert effective.tts.speed == 1.5 + + +def test_dograh_v2_rejects_out_of_range_speed(): with pytest.raises(ValidationError): OrganizationAIModelConfigurationV2( mode="dograh", dograh=DograhManagedAIModelConfiguration( api_key="mps-secret", - speed=1.5, + speed=2.5, ), ) @@ -238,6 +252,33 @@ def test_legacy_all_dograh_pipeline_converts_to_dograh_v2(): assert config.dograh.api_key == "mps-secret" +def test_legacy_dograh_pipeline_conversion_preserves_numeric_speed(): + legacy = EffectiveAIModelConfiguration( + llm=DograhLLMService( + provider="dograh", + api_key=["mps-secret"], + model="default", + ), + tts=DograhTTSService( + provider="dograh", + api_key=["mps-secret"], + model="default", + voice="default", + speed=1.5, + ), + stt=DograhSTTService( + provider="dograh", + api_key=["mps-secret"], + model="default", + ), + ) + + config = convert_legacy_ai_model_configuration_to_v2(legacy) + + assert config.mode == "dograh" + assert config.dograh.speed == 1.5 + + def test_legacy_mixed_dograh_pipeline_converts_to_dograh_v2(): legacy = EffectiveAIModelConfiguration( llm=OpenAILLMService( @@ -401,6 +442,7 @@ async def test_migrate_model_configuration_v2_initializes_hosted_mps_billing( ensure_billing = AsyncMock(return_value={"billing_mode": "v2"}) upsert = AsyncMock() migrate_workflows = AsyncMock() + sync_posthog_billing = Mock() monkeypatch.setattr(organization_routes, "DEPLOYMENT_MODE", "saas") monkeypatch.setattr( @@ -438,6 +480,11 @@ async def test_migrate_model_configuration_v2_initializes_hosted_mps_billing( "_model_configuration_v2_response", AsyncMock(return_value=expected_response), ) + monkeypatch.setattr( + organization_routes, + "_sync_posthog_organization_mps_billing_v2_status", + sync_posthog_billing, + ) user = SimpleNamespace( id=7, @@ -456,4 +503,5 @@ async def test_migrate_model_configuration_v2_initializes_hosted_mps_billing( organization_id=42, fallback_user_config=legacy, ) + sync_posthog_billing.assert_called_once_with(42, uses_mps_billing_v2=True) assert response == expected_response diff --git a/api/tests/test_auth_depends.py b/api/tests/test_auth_depends.py index 2f33ff58..8b82d379 100644 --- a/api/tests/test_auth_depends.py +++ b/api/tests/test_auth_depends.py @@ -19,10 +19,13 @@ async def test_get_user_initializes_hosted_mps_billing_for_new_org(monkeypatch): provider_id="stack-user-1", selected_organization_id=None, ) - organization = SimpleNamespace(id=42) + organization = SimpleNamespace(id=42, provider_id="team-1") existing_config = SimpleNamespace(llm=object(), tts=None, stt=None) ensure_billing = AsyncMock(return_value={"billing_mode": "v2"}) + group_calls = [] + capture_calls = [] + person_calls = [] monkeypatch.setattr(auth_depends, "AUTH_PROVIDER", "stack") monkeypatch.setattr( @@ -60,9 +63,247 @@ async def test_get_user_initializes_hosted_mps_billing_for_new_org(monkeypatch): "ensure_hosted_mps_billing_account_v2", ensure_billing, ) + monkeypatch.setattr( + auth_depends, + "group_identify", + lambda *args, **kwargs: group_calls.append((args, kwargs)), + ) + monkeypatch.setattr( + auth_depends, + "capture_event", + lambda *args, **kwargs: capture_calls.append((args, kwargs)), + ) + monkeypatch.setattr( + auth_depends, + "set_person_properties", + lambda *args, **kwargs: person_calls.append((args, kwargs)), + ) result = await auth_depends.get_user(authorization="Bearer token") assert result is user assert result.selected_organization_id == 42 ensure_billing.assert_awaited_once_with(42, created_by="stack-user-1") + + assert len(group_calls) == 1 + group_args, group_kwargs = group_calls[0] + assert group_args == ( + "organization", + "42", + { + "organization_id": 42, + "organization_provider_id": "team-1", + "auth_provider": "stack", + "created_by_provider_id": "stack-user-1", + }, + ) + assert group_kwargs == {"distinct_id": "stack-user-1"} + + assert len(person_calls) == 1 + person_args, person_kwargs = person_calls[0] + assert person_args == ( + "stack-user-1", + { + "user_id": 7, + "user_provider_id": "stack-user-1", + "selected_organization_id": 42, + "selected_organization_provider_id": "team-1", + }, + ) + assert person_kwargs == {} + + assert len(capture_calls) == 2 + org_created_args, org_created_kwargs = capture_calls[0] + assert org_created_args == () + assert org_created_kwargs["distinct_id"] == "stack-user-1" + assert org_created_kwargs["event"] == auth_depends.PostHogEvent.ORGANIZATION_CREATED + assert org_created_kwargs["groups"] == {"organization": "42"} + assert org_created_kwargs["properties"] == { + "organization_id": 42, + "organization_provider_id": "team-1", + "auth_provider": "stack", + "created_by_provider_id": "stack-user-1", + } + + association_args, association_kwargs = capture_calls[1] + assert association_args == () + assert association_kwargs["distinct_id"] == "stack-user-1" + assert ( + association_kwargs["event"] + == auth_depends.PostHogEvent.ORGANIZATION_USER_ASSOCIATED + ) + assert association_kwargs["groups"] == {"organization": "42"} + assert association_kwargs["properties"] == { + "user_id": 7, + "organization_id": 42, + "organization_provider_id": "team-1", + "auth_provider": "stack", + "organization_was_created": True, + } + + +def test_associate_user_with_posthog_org_supports_backfill_arguments(monkeypatch): + user = SimpleNamespace( + id=7, + email="user@example.com", + provider_id="stack-user-1", + selected_organization_id=99, + ) + organization = SimpleNamespace(id=42, provider_id="team-1") + person_calls = [] + capture_calls = [] + + monkeypatch.setattr( + auth_depends, + "set_person_properties", + lambda *args, **kwargs: person_calls.append((args, kwargs)), + ) + monkeypatch.setattr( + auth_depends, + "capture_event", + lambda *args, **kwargs: capture_calls.append((args, kwargs)), + ) + + auth_depends._associate_user_with_posthog_organization( + user=user, + organization=organization, + user_distinct_id="stack-user-1", + org_was_created=False, + organization_ids=[42, 99], + selected_organization_id=99, + selected_organization_provider_id="team-99", + ) + + assert person_calls == [ + ( + ( + "stack-user-1", + { + "user_id": 7, + "user_provider_id": "stack-user-1", + "selected_organization_id": 99, + "selected_organization_provider_id": "team-99", + "organization_ids": [42, 99], + "email": "user@example.com", + }, + ), + {}, + ) + ] + + assert len(capture_calls) == 1 + _, capture_kwargs = capture_calls[0] + assert capture_kwargs["distinct_id"] == "stack-user-1" + assert ( + capture_kwargs["event"] + == auth_depends.PostHogEvent.ORGANIZATION_USER_ASSOCIATED + ) + assert capture_kwargs["groups"] == {"organization": "42"} + assert capture_kwargs["properties"] == { + "user_id": 7, + "organization_id": 42, + "organization_provider_id": "team-1", + "auth_provider": "stack", + "organization_was_created": False, + } + assert "backfilled" not in capture_kwargs["properties"] + + +def test_sync_created_organization_to_posthog_supports_billing_flag(monkeypatch): + organization = SimpleNamespace(id=42, provider_id="team-1") + group_calls = [] + capture_calls = [] + + monkeypatch.setattr( + auth_depends, + "group_identify", + lambda *args, **kwargs: group_calls.append((args, kwargs)), + ) + monkeypatch.setattr( + auth_depends, + "capture_event", + lambda *args, **kwargs: capture_calls.append((args, kwargs)), + ) + + auth_depends._sync_created_organization_to_posthog( + organization=organization, + created_by_provider_id="stack-user-1", + uses_mps_billing_v2=True, + ) + + _, group_kwargs = group_calls[0] + group_args, _ = group_calls[0] + assert group_args == ( + "organization", + "42", + { + "organization_id": 42, + "organization_provider_id": "team-1", + "auth_provider": "stack", + "created_by_provider_id": "stack-user-1", + "uses_mps_billing_v2": True, + }, + ) + assert group_kwargs == {"distinct_id": "stack-user-1"} + + _, capture_kwargs = capture_calls[0] + assert capture_kwargs["distinct_id"] == "stack-user-1" + assert capture_kwargs["properties"]["uses_mps_billing_v2"] is True + + +def test_sync_posthog_organization_group_properties_has_no_distinct_id(monkeypatch): + organization = SimpleNamespace(id=42, provider_id="team-1") + group_calls = [] + + monkeypatch.setattr( + auth_depends, + "group_identify", + lambda *args, **kwargs: group_calls.append((args, kwargs)), + ) + + auth_depends._sync_posthog_organization_group_properties( + organization=organization, + uses_mps_billing_v2=True, + ) + + assert group_calls == [ + ( + ( + "organization", + "42", + { + "organization_id": 42, + "organization_provider_id": "team-1", + "auth_provider": "stack", + "uses_mps_billing_v2": True, + }, + ), + {}, + ) + ] + + +def test_sync_posthog_organization_mps_billing_v2_status(monkeypatch): + group_calls = [] + + monkeypatch.setattr( + auth_depends, + "group_identify", + lambda *args, **kwargs: group_calls.append((args, kwargs)), + ) + + auth_depends._sync_posthog_organization_mps_billing_v2_status( + 42, + uses_mps_billing_v2=True, + ) + + assert group_calls == [ + ( + ( + "organization", + "42", + {"uses_mps_billing_v2": True}, + ), + {}, + ) + ] diff --git a/api/tests/test_cartesia_tts_service_factory.py b/api/tests/test_cartesia_tts_service_factory.py index 71e2acab..fe155143 100644 --- a/api/tests/test_cartesia_tts_service_factory.py +++ b/api/tests/test_cartesia_tts_service_factory.py @@ -43,3 +43,35 @@ def test_create_cartesia_tts_service_passes_selected_model(): assert kwargs["api_key"] == "test-key" assert kwargs["settings"].model == "sonic-3.5" assert kwargs["settings"].voice == "test-voice-id" + + +def test_cartesia_tts_configuration_default_language_is_english(): + config = CartesiaTTSConfiguration(api_key="test-key") + + assert config.language == "en" + + +def test_create_cartesia_tts_service_passes_language_to_settings(): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.CARTESIA.value, + api_key="test-key", + model="sonic-3.5", + voice="test-voice-id", + speed=1.0, + volume=1.0, + language="tr", + ) + ) + audio_config = SimpleNamespace( + transport_out_sample_rate=24000, + transport_in_sample_rate=16000, + ) + + with patch( + "api.services.pipecat.service_factory.CartesiaTTSService" + ) as mock_service: + create_tts_service(user_config, audio_config) + + kwargs = mock_service.call_args.kwargs + assert kwargs["settings"].language == "tr" diff --git a/api/tests/test_deepgram_flux_service_factory.py b/api/tests/test_deepgram_flux_service_factory.py new file mode 100644 index 00000000..e94dff21 --- /dev/null +++ b/api/tests/test_deepgram_flux_service_factory.py @@ -0,0 +1,70 @@ +from types import SimpleNamespace +from unittest.mock import patch + +from pipecat.services.settings import NOT_GIVEN +from pipecat.transcriptions.language import Language + +from api.services.configuration.registry import ( + DeepgramSTTConfiguration, + ServiceProviders, +) +from api.services.pipecat.audio_config import AudioConfig +from api.services.pipecat.service_factory import create_stt_service + + +def test_deepgram_stt_schema_includes_flux_multilingual_language_options(): + language_schema = DeepgramSTTConfiguration.model_json_schema()["properties"][ + "language" + ] + + assert "flux-general-multi" in language_schema["model_options"] + assert "multi" in language_schema["model_options"]["flux-general-multi"] + assert "es" in language_schema["model_options"]["flux-general-multi"] + + +def test_create_deepgram_flux_multi_uses_flux_service_with_language_hint(): + user_config = SimpleNamespace( + stt=SimpleNamespace( + provider=ServiceProviders.DEEPGRAM.value, + api_key="test-key", + model="flux-general-multi", + language="es", + ) + ) + audio_config = AudioConfig( + transport_in_sample_rate=16000, + transport_out_sample_rate=16000, + ) + + with patch( + "api.services.pipecat.service_factory.DeepgramFluxSTTService" + ) as mock_service: + create_stt_service(user_config, audio_config) + + kwargs = mock_service.call_args.kwargs + assert kwargs["settings"].model == "flux-general-multi" + assert kwargs["settings"].language_hints == [Language.ES] + + +def test_create_deepgram_flux_multi_omits_auto_detect_language_hint(): + user_config = SimpleNamespace( + stt=SimpleNamespace( + provider=ServiceProviders.DEEPGRAM.value, + api_key="test-key", + model="flux-general-multi", + language="multi", + ) + ) + audio_config = AudioConfig( + transport_in_sample_rate=16000, + transport_out_sample_rate=16000, + ) + + with patch( + "api.services.pipecat.service_factory.DeepgramFluxSTTService" + ) as mock_service: + create_stt_service(user_config, audio_config) + + kwargs = mock_service.call_args.kwargs + assert kwargs["settings"].model == "flux-general-multi" + assert kwargs["settings"].language_hints is NOT_GIVEN diff --git a/api/tests/test_dograh_managed_correlation.py b/api/tests/test_dograh_managed_correlation.py index b0cb52c0..e182c495 100644 --- a/api/tests/test_dograh_managed_correlation.py +++ b/api/tests/test_dograh_managed_correlation.py @@ -22,6 +22,20 @@ class _FakeWebSocket: self.state = State.CLOSED +class _IterableFakeWebSocket(_FakeWebSocket): + def __init__(self, incoming_messages: list[dict]): + super().__init__() + self.incoming_messages = [json.dumps(message) for message in incoming_messages] + + def __aiter__(self): + return self + + async def __anext__(self) -> str: + if not self.incoming_messages: + raise StopAsyncIteration + return self.incoming_messages.pop(0) + + def test_dograh_llm_uses_explicit_mps_correlation_id(): service = DograhLLMService( api_key="mps-secret", @@ -108,3 +122,25 @@ async def test_dograh_tts_messages_use_explicit_mps_correlation_id(monkeypatch): assert fake_ws.messages[1]["type"] == "create_context" assert fake_ws.messages[1]["correlation_id"] == "mps-corr-123" assert fake_ws.messages[1]["mps_billing_version"] == "2" + + +@pytest.mark.asyncio +async def test_dograh_tts_final_for_missing_context_is_ignored(): + service = DograhTTSService(api_key="mps-secret") + service._websocket = _IterableFakeWebSocket( + [{"type": "final", "context_id": "ctx-already-removed"}] + ) + service._remote_initialized_context_ids.add("ctx-already-removed") + + remove_calls = [] + + async def fake_remove_audio_context(context_id: str): + remove_calls.append(context_id) + + service.audio_context_available = lambda context_id: False + service.remove_audio_context = fake_remove_audio_context + + await service._receive_messages() + + assert remove_calls == [] + assert "ctx-already-removed" not in service._remote_initialized_context_ids diff --git a/api/tests/test_huggingface_stt_service_factory.py b/api/tests/test_huggingface_stt_service_factory.py new file mode 100644 index 00000000..e7516c8a --- /dev/null +++ b/api/tests/test_huggingface_stt_service_factory.py @@ -0,0 +1,131 @@ +from types import SimpleNamespace +from unittest.mock import patch + +from api.services.configuration.check_validity import UserConfigurationValidator +from api.services.configuration.registry import ( + REGISTRY, + HuggingFaceLLMConfiguration, + HuggingFaceSTTConfiguration, + ServiceProviders, + ServiceType, +) +from api.services.pipecat.service_factory import ( + create_llm_service, + create_stt_service, +) + + +def test_huggingface_stt_configuration_defaults_and_registry(): + config = HuggingFaceSTTConfiguration(api_key="hf_test") + + assert config.provider == ServiceProviders.HUGGINGFACE + assert config.model == "openai/whisper-large-v3-turbo" + assert config.base_url == "https://router.huggingface.co/hf-inference" + assert config.return_timestamps is False + assert ( + REGISTRY[ServiceType.STT][ServiceProviders.HUGGINGFACE] + is HuggingFaceSTTConfiguration + ) + + +def test_huggingface_llm_configuration_defaults_and_registry(): + config = HuggingFaceLLMConfiguration(api_key="hf_test") + + assert config.provider == ServiceProviders.HUGGINGFACE + assert config.model == "openai/gpt-oss-120b:cerebras" + assert config.base_url == "https://router.huggingface.co/v1" + assert ( + REGISTRY[ServiceType.LLM][ServiceProviders.HUGGINGFACE] + is HuggingFaceLLMConfiguration + ) + + +def test_create_huggingface_llm_service_uses_openai_compatible_router(): + user_config = SimpleNamespace( + llm=SimpleNamespace( + provider=ServiceProviders.HUGGINGFACE.value, + api_key="hf_test", + model="deepseek-ai/DeepSeek-R1:fastest", + base_url="https://router.huggingface.co/v1", + bill_to="demo-org", + ) + ) + + with patch( + "api.services.pipecat.service_factory.HuggingFaceLLMService" + ) as mock_service: + create_llm_service(user_config) + + assert mock_service.call_count == 1 + kwargs = mock_service.call_args.kwargs + assert kwargs["api_key"] == "hf_test" + assert kwargs["base_url"] == "https://router.huggingface.co/v1" + assert kwargs["bill_to"] == "demo-org" + assert kwargs["settings"].model == "deepseek-ai/DeepSeek-R1:fastest" + assert kwargs["settings"].temperature == 0.1 + + +def test_create_huggingface_stt_service_uses_hosted_defaults(): + user_config = SimpleNamespace( + stt=SimpleNamespace( + provider=ServiceProviders.HUGGINGFACE.value, + api_key="hf_test", + model="openai/whisper-large-v3-turbo", + base_url="https://router.huggingface.co/hf-inference", + bill_to="demo-org", + return_timestamps=True, + ) + ) + audio_config = SimpleNamespace(transport_in_sample_rate=16000) + + with patch( + "api.services.pipecat.service_factory.HuggingFaceSTTService" + ) as mock_service: + create_stt_service(user_config, audio_config) + + assert mock_service.call_count == 1 + kwargs = mock_service.call_args.kwargs + assert kwargs["api_key"] == "hf_test" + assert kwargs["base_url"] == "https://router.huggingface.co/hf-inference" + assert kwargs["bill_to"] == "demo-org" + assert kwargs["sample_rate"] == 16000 + assert kwargs["settings"].model == "openai/whisper-large-v3-turbo" + assert kwargs["settings"].return_timestamps is True + + +def test_validator_accepts_huggingface_stt_token_format(): + validator = UserConfigurationValidator() + + assert ( + validator._validate_service( + HuggingFaceSTTConfiguration(api_key="hf_test"), + "stt", + ) + == [] + ) + assert ( + validator._validate_service( + HuggingFaceLLMConfiguration(api_key="hf_test"), + "llm", + ) + == [] + ) + + +def test_validator_rejects_non_huggingface_token_format(): + validator = UserConfigurationValidator() + + errors = validator._validate_service( + HuggingFaceSTTConfiguration(api_key="not-hf-token"), + "stt", + ) + + assert errors == [ + { + "model": "stt", + "message": ( + "Invalid Hugging Face API token format. Use a token that starts with " + "'hf_' and has Inference Providers permission." + ), + } + ] diff --git a/api/tests/test_inworld_tts_service_factory.py b/api/tests/test_inworld_tts_service_factory.py new file mode 100644 index 00000000..f2d063d7 --- /dev/null +++ b/api/tests/test_inworld_tts_service_factory.py @@ -0,0 +1,54 @@ +from types import SimpleNamespace +from unittest.mock import patch + +from api.services.configuration.registry import ( + InworldTTSConfiguration, + ServiceProviders, +) +from api.services.pipecat.service_factory import create_tts_service + + +def test_inworld_tts_configuration_defaults(): + config = InworldTTSConfiguration(api_key="test-key") + + assert config.provider == ServiceProviders.INWORLD + assert config.model == "inworld-tts-2" + assert config.voice == "Ashley" + assert config.language == "en-US" + assert config.delivery_mode == "BALANCED" + + +def test_create_inworld_tts_service_uses_websocket_service_without_http_session(): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.INWORLD.value, + api_key="test-key", + model="inworld-tts-2", + voice="Ashley", + speed=1.1, + language="en-US", + delivery_mode="CREATIVE", + ) + ) + audio_config = SimpleNamespace( + transport_out_sample_rate=24000, + transport_in_sample_rate=16000, + ) + + with ( + patch("api.services.pipecat.service_factory.aiohttp.ClientSession") as session, + patch("api.services.pipecat.service_factory.InworldTTSService") as mock_service, + ): + create_tts_service(user_config, audio_config) + + session.assert_not_called() + assert mock_service.call_count == 1 + kwargs = mock_service.call_args.kwargs + assert kwargs["api_key"] == "test-key" + assert "aiohttp_session" not in kwargs + assert "streaming" not in kwargs + assert kwargs["settings"].model == "inworld-tts-2" + assert kwargs["settings"].voice == "Ashley" + assert kwargs["settings"].language == "en-US" + assert kwargs["settings"].speaking_rate == 1.1 + assert kwargs["settings"].delivery_mode == "CREATIVE" diff --git a/api/tests/test_mcp_create_workflow.py b/api/tests/test_mcp_create_workflow.py new file mode 100644 index 00000000..aa8d2c11 --- /dev/null +++ b/api/tests/test_mcp_create_workflow.py @@ -0,0 +1,66 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from api.mcp_server.tools.create_workflow import create_workflow + + +@pytest.mark.asyncio +async def test_create_workflow_rejects_duplicate_api_triggers(): + user = MagicMock() + user.id = 1 + user.selected_organization_id = 1 + payload = { + "nodes": [ + { + "id": "start-1", + "type": "startCall", + "position": {"x": 0, "y": 0}, + "data": {"name": "Start", "prompt": "Greet."}, + }, + { + "id": "trigger-1", + "type": "trigger", + "position": {"x": 0, "y": 200}, + "data": {"name": "Trigger A", "trigger_path": "support_west"}, + }, + { + "id": "trigger-2", + "type": "trigger", + "position": {"x": 0, "y": 400}, + "data": {"name": "Trigger B", "trigger_path": "support_east"}, + }, + ], + "edges": [], + } + + with ( + patch( + "api.mcp_server.tools.create_workflow.authenticate_mcp_request", + AsyncMock(return_value=user), + ), + patch( + "api.mcp_server.tools.create_workflow.parse_code", + AsyncMock( + return_value={ + "ok": True, + "workflowName": "duplicate-trigger-test", + "workflow": payload, + } + ), + ), + patch( + "api.mcp_server.tools.create_workflow.reconcile_positions", + return_value=payload, + ), + patch( + "api.mcp_server.tools.create_workflow.db_client.create_workflow", + AsyncMock(), + ) as create_mock, + ): + result = await create_workflow(code="ignored") + + assert result["created"] is False + assert result["error_code"] == "graph_validation" + assert "at most one API Trigger" in result["error"] + create_mock.assert_not_awaited() diff --git a/api/tests/test_mcp_save_workflow.py b/api/tests/test_mcp_save_workflow.py index e877b5e1..9b6b5ae1 100644 --- a/api/tests/test_mcp_save_workflow.py +++ b/api/tests/test_mcp_save_workflow.py @@ -244,6 +244,58 @@ const only = wf.addTyped(endCall({ name: "only", prompt: "bye" })); update_mock.assert_not_awaited() +@pytest.mark.asyncio +async def test_graph_validation_catches_duplicate_api_triggers(mock_backends): + save_mock, update_mock = mock_backends + payload = { + "nodes": [ + { + "id": "start-1", + "type": "startCall", + "position": {"x": 0, "y": 0}, + "data": {"name": "Start", "prompt": "Greet."}, + }, + { + "id": "trigger-1", + "type": "trigger", + "position": {"x": 0, "y": 200}, + "data": {"name": "Trigger A", "trigger_path": "support_west"}, + }, + { + "id": "trigger-2", + "type": "trigger", + "position": {"x": 0, "y": 400}, + "data": {"name": "Trigger B", "trigger_path": "support_east"}, + }, + ], + "edges": [], + } + + with ( + patch( + "api.mcp_server.tools.save_workflow.parse_code", + AsyncMock( + return_value={ + "ok": True, + "workflowName": _FakeWorkflowModel.name, + "workflow": payload, + } + ), + ), + patch( + "api.mcp_server.tools.save_workflow.reconcile_positions", + return_value=payload, + ), + ): + result = await save_workflow(workflow_id=1, code="ignored") + + assert result["saved"] is False + assert result["error_code"] == "graph_validation" + assert "at most one API Trigger" in result["error"] + save_mock.assert_not_awaited() + update_mock.assert_not_awaited() + + # ─── Workflow not found / unauthorized ─────────────────────────────────── diff --git a/api/tests/test_node_specs.py b/api/tests/test_node_specs.py index b4797dff..b28f8d08 100644 --- a/api/tests/test_node_specs.py +++ b/api/tests/test_node_specs.py @@ -414,4 +414,9 @@ def test_to_mcp_dict_retains_authoring_signal_startcall(): ] # graph_constraints drops its null sub-fields. - assert projected["graph_constraints"] == {"min_incoming": 0, "max_incoming": 0} + assert projected["graph_constraints"] == { + "min_incoming": 0, + "max_incoming": 0, + "min_instances": 1, + "max_instances": 1, + } diff --git a/api/tests/test_onboarding_state.py b/api/tests/test_onboarding_state.py new file mode 100644 index 00000000..cb2f082c --- /dev/null +++ b/api/tests/test_onboarding_state.py @@ -0,0 +1,131 @@ +from datetime import UTC, datetime +from unittest.mock import AsyncMock, MagicMock, patch + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from api.routes.user import router +from api.schemas.onboarding_state import OnboardingState, OnboardingStateUpdate +from api.services.auth.depends import get_user + + +def _make_test_app(): + app = FastAPI() + app.include_router(router) + + mock_user = MagicMock() + mock_user.id = 1 + mock_user.is_superuser = False + mock_user.selected_organization_id = None + + app.dependency_overrides[get_user] = lambda: mock_user + return app + + +class TestOnboardingStateUpdateMerge: + def test_lists_union_without_duplicates(self): + state = OnboardingState( + seen_tooltips=["web_call"], completed_actions=["web_call_started"] + ) + update = OnboardingStateUpdate( + seen_tooltips=["web_call", "customize_workflow"], + completed_actions=["welcome_form_completed"], + ) + + merged = update.apply_to(state) + + assert merged.seen_tooltips == ["web_call", "customize_workflow"] + assert merged.completed_actions == [ + "web_call_started", + "welcome_form_completed", + ] + + def test_omitted_fields_preserve_existing_state(self): + completed_at = datetime(2026, 6, 12, tzinfo=UTC) + state = OnboardingState( + completed_at=completed_at, skipped=True, seen_tooltips=["web_call"] + ) + + merged = OnboardingStateUpdate().apply_to(state) + + assert merged.completed_at == completed_at + assert merged.skipped is True + assert merged.seen_tooltips == ["web_call"] + + def test_scalars_overwrite_when_supplied(self): + state = OnboardingState() + completed_at = datetime(2026, 6, 12, tzinfo=UTC) + + merged = OnboardingStateUpdate( + completed_at=completed_at, skipped=True + ).apply_to(state) + + assert merged.completed_at == completed_at + assert merged.skipped is True + + +class TestOnboardingStateRoutes: + def test_get_returns_defaults_when_no_row(self): + app = _make_test_app() + client = TestClient(app) + + with patch( + "api.services.user_onboarding.db_client.get_user_configuration_value", + new=AsyncMock(return_value=None), + ): + response = client.get("/user/onboarding-state") + + assert response.status_code == 200 + body = response.json() + assert body["completed_at"] is None + assert body["skipped"] is False + assert body["seen_tooltips"] == [] + assert body["completed_actions"] == [] + + def test_get_returns_defaults_on_invalid_stored_value(self): + app = _make_test_app() + client = TestClient(app) + + with patch( + "api.services.user_onboarding.db_client.get_user_configuration_value", + new=AsyncMock(return_value={"skipped": "not-a-bool"}), + ): + response = client.get("/user/onboarding-state") + + assert response.status_code == 200 + assert response.json()["skipped"] is False + + def test_put_merges_into_stored_state_and_persists(self): + app = _make_test_app() + client = TestClient(app) + + existing = {"seen_tooltips": ["web_call"]} + upsert = AsyncMock(side_effect=lambda user_id, key, value: value) + with ( + patch( + "api.services.user_onboarding.db_client.get_user_configuration_value", + new=AsyncMock(return_value=existing), + ), + patch( + "api.services.user_onboarding.db_client.upsert_user_configuration_value", + new=upsert, + ), + ): + response = client.put( + "/user/onboarding-state", + json={ + "completed_at": "2026-06-12T00:00:00Z", + "seen_tooltips": ["customize_workflow"], + }, + ) + + assert response.status_code == 200 + body = response.json() + assert body["seen_tooltips"] == ["web_call", "customize_workflow"] + assert body["completed_at"] is not None + + upsert.assert_awaited_once() + user_id, key, stored = upsert.await_args.args + assert user_id == 1 + assert key == "ONBOARDING" + assert stored["seen_tooltips"] == ["web_call", "customize_workflow"] diff --git a/api/tests/test_openai_tts_service_factory.py b/api/tests/test_openai_tts_service_factory.py new file mode 100644 index 00000000..cb44f4b1 --- /dev/null +++ b/api/tests/test_openai_tts_service_factory.py @@ -0,0 +1,31 @@ +from types import SimpleNamespace +from unittest.mock import patch + +from pipecat.services.openai._constants import OPENAI_SAMPLE_RATE + +from api.services.configuration.registry import ServiceProviders +from api.services.pipecat.service_factory import create_tts_service + + +def test_create_openai_tts_service_uses_openai_pcm_sample_rate(): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.OPENAI.value, + api_key="test-key", + model="gpt-4o-mini-tts", + voice="alloy", + base_url=None, + ) + ) + audio_config = SimpleNamespace( + transport_out_sample_rate=16000, + transport_in_sample_rate=16000, + ) + + with patch("api.services.pipecat.service_factory.OpenAITTSService") as mock_service: + create_tts_service(user_config, audio_config) + + assert mock_service.call_count == 1 + kwargs = mock_service.call_args.kwargs + assert kwargs["sample_rate"] == OPENAI_SAMPLE_RATE + assert kwargs["settings"].model == "gpt-4o-mini-tts" diff --git a/api/tests/test_posthog_client.py b/api/tests/test_posthog_client.py new file mode 100644 index 00000000..cbf9919d --- /dev/null +++ b/api/tests/test_posthog_client.py @@ -0,0 +1,34 @@ +from api.services import posthog_client + + +class FakePostHog: + def __init__(self): + self.group_identify_calls = [] + + def group_identify(self, *args, **kwargs): + self.group_identify_calls.append((args, kwargs)) + + +def test_group_identify_uses_stable_server_distinct_id(monkeypatch): + fake_posthog = FakePostHog() + monkeypatch.setattr(posthog_client, "get_posthog", lambda: fake_posthog) + + posthog_client.group_identify("organization", "42", {}) + + _, kwargs = fake_posthog.group_identify_calls[0] + assert kwargs["distinct_id"] == "server-group-identify" + + +def test_group_identify_preserves_real_distinct_id(monkeypatch): + fake_posthog = FakePostHog() + monkeypatch.setattr(posthog_client, "get_posthog", lambda: fake_posthog) + + posthog_client.group_identify( + "organization", + "42", + {}, + distinct_id="stack-user-1", + ) + + _, kwargs = fake_posthog.group_identify_calls[0] + assert kwargs["distinct_id"] == "stack-user-1" diff --git a/api/tests/test_qa_analysis_non_dict_response.py b/api/tests/test_qa_analysis_non_dict_response.py new file mode 100644 index 00000000..1d150972 --- /dev/null +++ b/api/tests/test_qa_analysis_non_dict_response.py @@ -0,0 +1,72 @@ +"""Regression test for QA analysis when the LLM returns a non-dict JSON value. + +``parse_llm_json`` is explicitly designed to return a list when the model emits +a top-level JSON array (see ``test_json_parser.py``). The QA analyzers then call +``parsed.get(...)`` on the result. For a list that raises ``AttributeError``, +which is NOT caught by the surrounding ``except (json.JSONDecodeError, ValueError)`` +— so a stray array response crashed the whole QA run instead of degrading to +empty results. +""" + +from types import SimpleNamespace +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from api.services.workflow.qa import analysis as qa_analysis + + +@pytest.mark.asyncio +async def test_whole_call_qa_tolerates_array_llm_response(): + """A top-level JSON array from the QA LLM degrades to empty results.""" + qa_data = SimpleNamespace(qa_system_prompt="Summarize: {transcript}") + workflow_run = SimpleNamespace( + logs={ + "realtime_feedback_events": [ + {"role": "user", "content": "hello"}, + {"role": "assistant", "content": "hi there"}, + ] + }, + usage_info={"call_duration_seconds": 12}, + ) + warning_mock = Mock() + + with ( + patch.object( + qa_analysis, "build_conversation_structure", return_value=[{"x": 1}] + ), + patch.object(qa_analysis, "format_transcript", return_value="user: hello"), + patch.object(qa_analysis, "compute_call_metrics", return_value={}), + patch.object( + qa_analysis, + "resolve_llm_config", + new=AsyncMock(return_value=("openai", "gpt-4o", "sk-test", {})), + ), + patch.object( + qa_analysis, "create_llm_service_from_provider", return_value=object() + ), + patch.object( + qa_analysis, + "_run_llm_inference", + new=AsyncMock(return_value='["tag1", "tag2"]'), + ), + patch.object(qa_analysis, "setup_langfuse_parent_context", return_value=None), + patch.object(qa_analysis, "add_qa_span_to_trace", return_value=None), + patch.object(qa_analysis.logger, "warning", warning_mock), + ): + # Before the fix this raised AttributeError: 'list' object has no + # attribute 'get'. + result = await qa_analysis._run_whole_call_qa_analysis( + qa_data, workflow_run, workflow_run_id=99 + ) + + node_result = result["node_results"]["whole_call"] + assert node_result["tags"] == [] + assert node_result["summary"] == "" + assert node_result["score"] is None + warning_mock.assert_called_once() + warning_message = warning_mock.call_args.args[0] + assert "non-object JSON" in warning_message + assert "run 99" in warning_message + assert "list" in warning_message + assert "tag1" not in warning_message diff --git a/api/tests/test_quota_service.py b/api/tests/test_quota_service.py index 8e2ee6f5..80b5e8c6 100644 --- a/api/tests/test_quota_service.py +++ b/api/tests/test_quota_service.py @@ -1,6 +1,7 @@ from types import SimpleNamespace from unittest.mock import AsyncMock +import httpx import pytest from api.services import quota_service @@ -284,6 +285,69 @@ async def test_authorize_workflow_run_managed_v2_stores_hosted_correlation( ) +@pytest.mark.asyncio +async def test_authorize_workflow_run_service_token_from_wrong_org_prompts_new_token( + monkeypatch, +): + api_key = "mps_sk_12345678" + get_config = AsyncMock( + return_value=_dograh_config(api_key, managed_service_version=2) + ) + request = httpx.Request( + "POST", + "http://localhost:8004/api/v1/billing/accounts/42/run-authorization", + ) + response = httpx.Response( + 403, + json={"detail": "Service key organization mismatch"}, + request=request, + ) + authorize = AsyncMock( + side_effect=httpx.HTTPStatusError( + "Failed to authorize MPS workflow run start", + request=request, + response=response, + ) + ) + + monkeypatch.setattr(quota_service, "DEPLOYMENT_MODE", "saas") + _patch_workflow_context(monkeypatch) + monkeypatch.setattr( + quota_service, + "get_effective_ai_model_configuration_for_workflow", + get_config, + ) + monkeypatch.setattr( + quota_service.mps_service_key_client, + "authorize_workflow_run_start", + authorize, + ) + monkeypatch.setattr( + quota_service.mps_service_key_client, + "check_service_key_usage", + AsyncMock(), + ) + + result = await quota_service.authorize_workflow_run_start( + workflow_id=7, + workflow_run_id=88, + ) + + assert result.has_quota is False + assert result.error_code == "service_key_org_mismatch" + assert result.error_message == quota_service.SERVICE_TOKEN_ORG_MISMATCH_MESSAGE + assert "new service token from the Developers tab" in result.error_message + authorize.assert_awaited_once_with( + organization_id=42, + workflow_run_id=88, + service_key=api_key, + require_correlation_id=True, + minimum_credits=quota_service.MINIMUM_DOGRAH_CREDITS_FOR_CALL, + created_by="provider-123", + metadata={"dograh_user_id": "123", "workflow_id": 7}, + ) + + @pytest.mark.asyncio async def test_authorize_workflow_run_oss_uses_key_paths_not_workflow_org( monkeypatch, diff --git a/api/tests/test_s3_signed_url.py b/api/tests/test_s3_signed_url.py new file mode 100644 index 00000000..95156dcb --- /dev/null +++ b/api/tests/test_s3_signed_url.py @@ -0,0 +1,30 @@ +from api.routes.s3_signed_url import ( + _extract_legacy_workflow_run_id, + _extract_org_id_from_key, +) + + +def test_split_recording_keys_are_workflow_run_artifacts_not_org_keys(): + assert _extract_legacy_workflow_run_id("recordings/1855/user.wav") == 1855 + assert _extract_legacy_workflow_run_id("recordings/1855/bot.wav") == 1855 + + assert _extract_org_id_from_key("recordings/1855/user.wav") is None + assert _extract_org_id_from_key("recordings/1855/bot.wav") is None + + +def test_legacy_recording_keys_do_not_fall_through_to_org_scoped_auth(): + assert _extract_legacy_workflow_run_id("recordings/1855.wav") == 1855 + assert _extract_legacy_workflow_run_id("recordings/1855/other.wav") is None + + assert _extract_org_id_from_key("recordings/1855.wav") is None + assert _extract_org_id_from_key("recordings/1855/other.wav") is None + + +def test_known_org_scoped_keys_extract_org_id(): + assert _extract_org_id_from_key("campaigns/42/source.csv") == 42 + assert _extract_org_id_from_key("knowledge_base/42/document/file.pdf") == 42 + assert _extract_legacy_workflow_run_id("campaigns/42/source.csv") is None + + +def test_unknown_numeric_prefix_is_not_treated_as_org_scoped(): + assert _extract_org_id_from_key("unknown/42/file.wav") is None diff --git a/api/tests/test_sarvam_service_factory.py b/api/tests/test_sarvam_service_factory.py index 3040769f..38b4d807 100644 --- a/api/tests/test_sarvam_service_factory.py +++ b/api/tests/test_sarvam_service_factory.py @@ -7,6 +7,7 @@ from pipecat.transcriptions.language import Language from api.services.configuration.registry import ( SarvamLLMConfiguration, + SarvamTTSConfiguration, ServiceProviders, ) from api.services.pipecat.audio_config import AudioConfig @@ -14,6 +15,7 @@ from api.services.pipecat.service_factory import ( create_llm_service, create_llm_service_from_provider, create_stt_service, + create_tts_service, ) @@ -112,3 +114,94 @@ class TestSarvamSTTServiceFactory: kwargs = mock_service.call_args.kwargs assert kwargs["settings"].language == expected_language + + +class TestSarvamTTSServiceFactory: + def test_sarvam_tts_configuration_defaults(self): + config = SarvamTTSConfiguration(api_key="test-key") + + assert config.provider == ServiceProviders.SARVAM + assert config.model == "bulbul:v2" + assert config.voice == "anushka" + assert config.language == "hi-IN" + assert config.speed == 1.0 + + def test_sarvam_tts_voice_schema_allows_custom_model_specific_options(self): + voice_schema = SarvamTTSConfiguration.model_json_schema()["properties"]["voice"] + + assert voice_schema["allow_custom_input"] is True + assert "bulbul:v2" in voice_schema["model_options"] + assert "bulbul:v3" in voice_schema["model_options"] + + def test_create_sarvam_tts_service_maps_speed_to_pace(self): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.SARVAM.value, + api_key="test-key", + model="bulbul:v2", + voice="anushka", + language="hi-IN", + speed=1.25, + ) + ) + audio_config = AudioConfig( + transport_in_sample_rate=16000, transport_out_sample_rate=16000 + ) + + with patch( + "api.services.pipecat.service_factory.SarvamTTSService" + ) as mock_service: + create_tts_service(user_config, audio_config) + + kwargs = mock_service.call_args.kwargs + assert kwargs["api_key"] == "test-key" + assert kwargs["settings"].model == "bulbul:v2" + assert kwargs["settings"].voice == "anushka" + assert kwargs["settings"].language == Language.HI + assert kwargs["settings"].pace == 1.25 + + def test_create_sarvam_tts_service_normalizes_custom_voice_id(self): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.SARVAM.value, + api_key="test-key", + model="bulbul:v2", + voice=" Rehan ", + language="hi-IN", + speed=1.0, + ) + ) + audio_config = AudioConfig( + transport_in_sample_rate=16000, transport_out_sample_rate=16000 + ) + + with patch( + "api.services.pipecat.service_factory.SarvamTTSService" + ) as mock_service: + create_tts_service(user_config, audio_config) + + kwargs = mock_service.call_args.kwargs + assert kwargs["settings"].voice == "rehan" + + def test_create_sarvam_tts_service_defaults_blank_voice_id(self): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.SARVAM.value, + api_key="test-key", + model="bulbul:v2", + voice=" ", + language="hi-IN", + speed=1.0, + ) + ) + audio_config = AudioConfig( + transport_in_sample_rate=16000, transport_out_sample_rate=16000 + ) + + with patch( + "api.services.pipecat.service_factory.SarvamTTSService" + ) as mock_service: + create_tts_service(user_config, audio_config) + + kwargs = mock_service.call_args.kwargs + assert kwargs["settings"].voice == "anushka" diff --git a/api/tests/test_smallest_service_factory.py b/api/tests/test_smallest_service_factory.py new file mode 100644 index 00000000..521fd2a8 --- /dev/null +++ b/api/tests/test_smallest_service_factory.py @@ -0,0 +1,80 @@ +from types import SimpleNamespace +from unittest.mock import patch + +from api.services.configuration.check_validity import UserConfigurationValidator +from api.services.configuration.registry import ( + REGISTRY, + ServiceProviders, + ServiceType, + SmallestAISTTConfiguration, + SmallestAITTSConfiguration, +) +from api.services.pipecat.service_factory import create_tts_service + + +def test_smallest_tts_configuration_defaults_and_registry(): + config = SmallestAITTSConfiguration(api_key="test-key") + + assert config.provider == ServiceProviders.SMALLEST + assert config.model == "lightning_v3.1" + assert config.voice == "sophia" + assert config.language == "en" + assert config.speed == 1.0 + assert ( + REGISTRY[ServiceType.TTS][ServiceProviders.SMALLEST] + is SmallestAITTSConfiguration + ) + + +def test_smallest_stt_configuration_defaults_and_registry(): + config = SmallestAISTTConfiguration(api_key="test-key") + + assert config.provider == ServiceProviders.SMALLEST + assert config.model == "pulse" + assert config.language == "en" + assert ( + REGISTRY[ServiceType.STT][ServiceProviders.SMALLEST] + is SmallestAISTTConfiguration + ) + + +def test_validator_accepts_smallest_services(): + validator = UserConfigurationValidator() + + assert ( + validator._validate_service( + SmallestAITTSConfiguration(api_key="test-key"), + "tts", + ) + == [] + ) + assert ( + validator._validate_service( + SmallestAISTTConfiguration(api_key="test-key"), + "stt", + ) + == [] + ) + + +def test_create_smallest_tts_service_normalizes_hyphenated_model_values(): + user_config = SimpleNamespace( + tts=SimpleNamespace( + provider=ServiceProviders.SMALLEST.value, + api_key="test-key", + model="lightning-v3.1", + voice="sophia", + language="en", + speed=1.0, + ) + ) + audio_config = SimpleNamespace(transport_in_sample_rate=16000) + + with patch( + "api.services.pipecat.service_factory.SmallestTTSService" + ) as mock_service: + create_tts_service(user_config, audio_config) + + assert mock_service.call_count == 1 + kwargs = mock_service.call_args.kwargs + assert kwargs["settings"].model == "lightning_v3.1" diff --git a/api/tests/test_text_chat_session_service.py b/api/tests/test_text_chat_session_service.py index abbba0e6..8ceb5ec8 100644 --- a/api/tests/test_text_chat_session_service.py +++ b/api/tests/test_text_chat_session_service.py @@ -9,6 +9,7 @@ from api.services.workflow.text_chat_session_service import ( TextChatTurnNotFoundError, _reload_text_chat_session, build_pending_text_chat_turn, + execute_pending_text_chat_turn, truncate_text_chat_future_turns, validate_text_chat_turn_cursor, ) @@ -77,6 +78,36 @@ async def test_reload_text_chat_session_uses_run_id_to_resolve_organization( get_text_session.assert_awaited_once_with(123, organization_id=77) +@pytest.mark.asyncio +async def test_execute_pending_turn_surfaces_original_exception_message(monkeypatch): + session = WorkflowRunTextSessionModel(workflow_run_id=42) + session.session_data = { + "turns": [{"id": "turn-1", "status": "pending"}], + "cursor_turn_id": "turn-1", + } + session.checkpoint = None + + monkeypatch.setattr( + text_chat_session_service, + "execute_text_chat_pending_turn", + AsyncMock(side_effect=RuntimeError("Workflow has 2 start nodes")), + ) + monkeypatch.setattr( + text_chat_session_service, + "_mark_pending_turn_failed", + AsyncMock(), + ) + + with pytest.raises( + TextChatSessionExecutionError, match="Workflow has 2 start nodes" + ): + await execute_pending_text_chat_turn( + workflow_id=1, + run_id=42, + text_session=session, + ) + + @pytest.mark.asyncio async def test_reload_text_chat_session_raises_when_run_organization_is_missing( monkeypatch, diff --git a/api/tests/test_trigger_path_validation.py b/api/tests/test_trigger_path_validation.py index 758eceb4..1b828450 100644 --- a/api/tests/test_trigger_path_validation.py +++ b/api/tests/test_trigger_path_validation.py @@ -54,3 +54,37 @@ def test_validate_trigger_paths_rejects_long_and_duplicate_paths(): in messages ) assert "Trigger path is duplicated in this workflow." in messages + + +def test_validate_trigger_paths_detects_duplicate_when_first_node_has_no_id(): + """A duplicate trigger path must be flagged even when the first node sharing + that path has no ``id`` (``node.get("id")`` is None). + + Regression: the duplicate check previously used ``seen_paths.get(path)`` and + treated a ``None`` result as "not seen yet", so a first node with a missing + id (stored as None) made every later node with the same path slip through + undetected. + """ + workflow_definition = { + "nodes": [ + # No "id" key -> node_id resolves to None. + {"type": "trigger", "data": {"trigger_path": "sales_agent"}}, + { + "id": "trigger-2", + "type": "trigger", + "data": {"trigger_path": "sales_agent"}, + }, + ], + "edges": [], + } + + issues = validate_trigger_paths(workflow_definition) + messages = [issue.message for issue in issues] + + assert "Trigger path is duplicated in this workflow." in messages + duplicate_issue = next( + issue + for issue in issues + if issue.message == "Trigger path is duplicated in this workflow." + ) + assert duplicate_issue.node_id == "trigger-2" diff --git a/api/tests/test_user_configuration_upsert.py b/api/tests/test_user_configuration_upsert.py new file mode 100644 index 00000000..2861354b --- /dev/null +++ b/api/tests/test_user_configuration_upsert.py @@ -0,0 +1,98 @@ +import pytest +from sqlalchemy import select +from sqlalchemy.dialects import postgresql + +from api.db.models import UserConfigurationModel +from api.db.user_client import UserClient +from api.enums import UserConfigurationKey + + +class _FakeResult: + def __init__(self, value: dict): + self._value = value + + def scalar_one(self) -> dict: + return self._value + + +class _FakeSession: + def __init__(self, result_value: dict): + self.result_value = result_value + self.statements = [] + self.committed = False + self.rolled_back = False + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + return False + + async def execute(self, stmt): + self.statements.append(stmt) + return _FakeResult(self.result_value) + + async def commit(self): + self.committed = True + + async def rollback(self): + self.rolled_back = True + + +@pytest.mark.asyncio +async def test_upsert_user_configuration_value_uses_atomic_conflict_update(): + result_value = {"completed_actions": ["web_call_started"]} + fake_session = _FakeSession(result_value) + client = UserClient.__new__(UserClient) + client.async_session = lambda: fake_session + + value = await client.upsert_user_configuration_value( + 86, + UserConfigurationKey.ONBOARDING.value, + result_value, + ) + + assert value == result_value + assert fake_session.committed is True + assert fake_session.rolled_back is False + assert len(fake_session.statements) == 1 + + compiled = str(fake_session.statements[0].compile(dialect=postgresql.dialect())) + assert "ON CONFLICT ON CONSTRAINT _user_configuration_key_uc DO UPDATE" in compiled + assert "configuration = excluded.configuration" in compiled + assert "last_validated_at" not in compiled + + +@pytest.mark.asyncio +async def test_upsert_user_configuration_value_updates_existing_row( + db_session, + async_session, +): + user, _ = await db_session.get_or_create_user_by_provider_id( + "user-config-upsert-test" + ) + + first = await db_session.upsert_user_configuration_value( + user.id, + UserConfigurationKey.ONBOARDING.value, + {"skipped": False}, + ) + second = await db_session.upsert_user_configuration_value( + user.id, + UserConfigurationKey.ONBOARDING.value, + {"skipped": True}, + ) + + assert first == {"skipped": False} + assert second == {"skipped": True} + + result = await async_session.execute( + select(UserConfigurationModel).where( + UserConfigurationModel.user_id == user.id, + UserConfigurationModel.key == UserConfigurationKey.ONBOARDING.value, + ) + ) + rows = result.scalars().all() + + assert len(rows) == 1 + assert rows[0].configuration == {"skipped": True} diff --git a/api/tests/test_workflow_create_route.py b/api/tests/test_workflow_create_route.py index 74338b7b..6c2b9850 100644 --- a/api/tests/test_workflow_create_route.py +++ b/api/tests/test_workflow_create_route.py @@ -47,3 +47,38 @@ def test_create_workflow_rejects_invalid_trigger_path_before_db_write(): assert detail["errors"][0]["field"] == "data.trigger_path" assert "single URL path segment" in detail["errors"][0]["message"] assert mock_db.mock_calls == [] + + +def test_create_workflow_rejects_duplicate_api_triggers_before_db_write(): + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + response = client.post( + "/workflow/create/definition", + json={ + "name": "Support Agent", + "workflow_definition": { + "nodes": [ + { + "id": "trigger-1", + "type": "trigger", + "data": {"trigger_path": "support_west"}, + }, + { + "id": "trigger-2", + "type": "trigger", + "data": {"trigger_path": "support_east"}, + }, + ], + "edges": [], + }, + }, + ) + + assert response.status_code == 422 + detail = response.json()["detail"] + assert detail["is_valid"] is False + assert detail["errors"][0]["kind"] == "workflow" + assert "at most one API Trigger" in detail["errors"][0]["message"] + assert mock_db.mock_calls == [] diff --git a/api/tests/test_workflow_graph_constraints.py b/api/tests/test_workflow_graph_constraints.py index bb351f8b..c7e405e3 100644 --- a/api/tests/test_workflow_graph_constraints.py +++ b/api/tests/test_workflow_graph_constraints.py @@ -72,14 +72,24 @@ _SCENARIOS = [ ( "no_start_node", ["no_start_node"], - ["Workflow has no start node"], + ["Workflow must have at least one Start Call"], ), # Two startCall nodes — surfaced separately from no_start_node so # the editor can show a count-specific message. ( "multiple_start_nodes", ["multiple_start_nodes:2"], - ["Workflow has 2 start nodes"], + ["Workflow can have at most one Start Call"], + ), + ( + "multiple_trigger_nodes", + ["max_instances_1:trigger:2"], + ["Workflow can have at most one API Trigger"], + ), + ( + "multiple_global_nodes", + ["max_instances_1:globalNode:2"], + ["Workflow can have at most one Global Node"], ), ] @@ -122,3 +132,35 @@ def test_workflow_graph_rejects_violations(name, expected_graph_messages): assert any(expected in m for m in actual_messages), ( f"Expected substring {expected!r} not found in graph errors: {actual_messages}" ) + + +def test_workflow_graph_can_skip_duplicate_api_trigger_check_for_runtime(): + raw, _ = _load("multiple_trigger_nodes") + dto = ReactFlowDTO.model_validate_json(raw) + + WorkflowGraph(dto, skip_instance_constraints_for={"trigger"}) + + +def test_workflow_graph_start_semantics_come_from_node_type_not_legacy_flag(): + dto = ReactFlowDTO.model_validate( + { + "nodes": [ + { + "id": "start-1", + "type": "startCall", + "position": {"x": 0, "y": 0}, + "data": { + "name": "Start", + "prompt": "Greet.", + "is_start": False, + }, + } + ], + "edges": [], + } + ) + + graph = WorkflowGraph(dto) + + assert graph.start_node_id == "start-1" + assert graph.nodes["start-1"].is_start is True diff --git a/api/tests/test_workflow_list_route.py b/api/tests/test_workflow_list_route.py index 0f1864b4..dcc2ddd5 100644 --- a/api/tests/test_workflow_list_route.py +++ b/api/tests/test_workflow_list_route.py @@ -2,6 +2,7 @@ from datetime import datetime, timezone from types import SimpleNamespace from unittest.mock import AsyncMock, patch +import pytest from fastapi import FastAPI from fastapi.testclient import TestClient @@ -50,3 +51,99 @@ def test_workflow_fetch_list_includes_workflow_uuid(): "workflow_uuid": workflow.workflow_uuid, } ] + + +def test_workflow_fetch_invalid_status_returns_422_without_db_query(): + """A status outside the workflow_status enum (e.g. 'published') must fail + as a clean 422 instead of a 500 from the Postgres enum cast.""" + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + mock_db.get_all_workflows_for_listing = AsyncMock() + mock_db.get_workflow_run_counts = AsyncMock() + + response = client.get("/workflow/fetch?status=published") + + assert response.status_code == 422 + assert "published" in response.json()["detail"] + # The invalid value must never reach the database layer. + mock_db.get_all_workflows_for_listing.assert_not_called() + + +def test_workflow_fetch_valid_single_status_passes_through(): + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + mock_db.get_all_workflows_for_listing = AsyncMock(return_value=[]) + mock_db.get_workflow_run_counts = AsyncMock(return_value={}) + + response = client.get("/workflow/fetch?status=active") + + assert response.status_code == 200 + mock_db.get_all_workflows_for_listing.assert_awaited_once_with( + organization_id=11, status="active" + ) + + +def test_workflow_fetch_comma_separated_status_queries_each_value(): + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + mock_db.get_all_workflows_for_listing = AsyncMock(return_value=[]) + mock_db.get_workflow_run_counts = AsyncMock(return_value={}) + + response = client.get("/workflow/fetch?status=active,archived") + + assert response.status_code == 200 + assert mock_db.get_all_workflows_for_listing.await_count == 2 + statuses = { + call.kwargs["status"] + for call in mock_db.get_all_workflows_for_listing.await_args_list + } + assert statuses == {"active", "archived"} + + +def test_workflow_fetch_mixed_valid_and_invalid_status_returns_422(): + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + mock_db.get_all_workflows_for_listing = AsyncMock() + mock_db.get_workflow_run_counts = AsyncMock() + + response = client.get("/workflow/fetch?status=active,published") + + assert response.status_code == 422 + mock_db.get_all_workflows_for_listing.assert_not_called() + + +@pytest.mark.parametrize("status", [" ", ",", "active,,archived"]) +def test_workflow_fetch_blank_status_token_returns_422_without_db_query(status: str): + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + mock_db.get_all_workflows_for_listing = AsyncMock() + mock_db.get_workflow_run_counts = AsyncMock() + + response = client.get("/workflow/fetch", params={"status": status}) + + assert response.status_code == 422 + assert "" in response.json()["detail"] + mock_db.get_all_workflows_for_listing.assert_not_called() + + +def test_workflow_summary_blank_status_token_returns_422_without_db_query(): + app = _make_test_app() + client = TestClient(app) + + with patch("api.routes.workflow.db_client") as mock_db: + mock_db.get_all_workflows = AsyncMock() + + response = client.get("/workflow/summary", params={"status": ","}) + + assert response.status_code == 422 + mock_db.get_all_workflows.assert_not_called() diff --git a/api/tests/test_workflow_run_billing.py b/api/tests/test_workflow_run_billing.py index 2837317f..1dbe1828 100644 --- a/api/tests/test_workflow_run_billing.py +++ b/api/tests/test_workflow_run_billing.py @@ -5,6 +5,7 @@ import pytest from api.services import workflow_run_billing as workflow_run_billing_mod from api.services.workflow_run_billing import ( + _is_usage_not_ready_error, report_completed_workflow_run_platform_usage, report_workflow_run_platform_usage, ) @@ -24,6 +25,16 @@ def _make_workflow_run(): ) +def test_is_usage_not_ready_error_detects_mps_409(): + exc = Exception("Failed to report platform usage") + exc.response = SimpleNamespace( + status_code=409, + text='{"detail":"usage_not_ready"}', + ) + + assert _is_usage_not_ready_error(exc) is True + + @pytest.mark.asyncio async def test_report_workflow_run_platform_usage_reports_hosted_completion( monkeypatch, diff --git a/api/utils/recording_artifacts.py b/api/utils/recording_artifacts.py new file mode 100644 index 00000000..42044cf9 --- /dev/null +++ b/api/utils/recording_artifacts.py @@ -0,0 +1,35 @@ +from typing import Literal + +RecordingTrack = Literal["mixed", "user", "bot"] + + +def get_recording_storage_key(extra: dict | None, track: RecordingTrack) -> str | None: + recordings = (extra or {}).get("recordings", {}) + if not isinstance(recordings, dict): + return None + + artifact = recordings.get(track) + if isinstance(artifact, str): + return artifact + if isinstance(artifact, dict): + storage_key = artifact.get("storage_key") + return storage_key if isinstance(storage_key, str) else None + return None + + +def get_recording_storage_backend( + extra: dict | None, track: RecordingTrack +) -> str | None: + recordings = (extra or {}).get("recordings", {}) + if not isinstance(recordings, dict): + return None + + artifact = recordings.get(track) + if isinstance(artifact, dict): + storage_backend = artifact.get("storage_backend") + return storage_backend if isinstance(storage_backend, str) else None + return None + + +def has_recording_track(extra: dict | None, track: RecordingTrack) -> bool: + return bool(get_recording_storage_key(extra, track)) diff --git a/docker-compose.yaml b/docker-compose.yaml index 40c65c7d..9ab4d430 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -164,6 +164,17 @@ services: # from this value and nginx load-balances across them with least_conn. FASTAPI_WORKERS: "${FASTAPI_WORKERS:-1}" + # Trust X-Forwarded-* headers from any peer so uvicorn honors nginx's + # `X-Forwarded-Proto: https`. nginx runs as its own container and reaches + # uvicorn from a Docker-network IP (not loopback), but uvicorn trusts only + # 127.0.0.1 by default — so without this it ignores the header, request.url + # comes back as http://…, and inbound webhook signature checks fail for + # every provider at once (telephony providers sign the public https URL, so + # the recomputed signature won't match). "*" trusts all peers; the api port + # (8000) is published, so firewall/harden it at the host — or narrow this + # to your Docker bridge subnet — if that exposure matters to you. + FORWARDED_ALLOW_IPS: "*" + # Langfuse — credentials can be set here or per-organization via the UI # at /settings. Tracing is automatically active when credentials are # available; uncomment to set defaults for all organizations. @@ -211,9 +222,13 @@ services: ui: image: ${REGISTRY:-dograhai}/dograh-ui:latest environment: + # Bind the Next.js standalone server to all interfaces + HOSTNAME: "0.0.0.0" + # Server-side URL (SSR, internal Docker network) BACKEND_URL: "${BACKEND_URL:-http://api:8000}" NODE_ENV: "oss" + # Flag to enable/ disable posthog ENABLE_TELEMETRY: "${ENABLE_TELEMETRY:-true}" @@ -229,7 +244,7 @@ services: test: [ "CMD-SHELL", - "wget --no-verbose --tries=1 --spider http://localhost:3010 || exit 1", + "wget --no-verbose --tries=1 --spider http://127.0.0.1:3010 || exit 1", ] interval: 30s timeout: 10s diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json index dfc4587b..b428005e 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"}}],"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"}}],"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"}}],"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"}}],"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"}}],"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"}}],"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}/folder":{"put":{"tags":["main"],"summary":"Move Workflow To Folder","description":"Move a workflow into a folder, or to \"Uncategorized\" (folder_id=null).\n\nValidates that the target folder belongs to the caller's organization \u2014\nthe FK alone proves the folder exists, not that the caller may use it.","operationId":"move_workflow_to_folder_api_v1_workflow__workflow_id__folder_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/MoveWorkflowToFolderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowListResponse"}}}},"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/workflow/{workflow_id}/text-chat/sessions":{"post":{"tags":["main","workflow-text-chat"],"summary":"Create Text Chat Session","operationId":"create_text_chat_session_api_v1_workflow__workflow_id__text_chat_sessions_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/CreateTextChatSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunTextSessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/text-chat/sessions/{run_id}":{"get":{"tags":["main","workflow-text-chat"],"summary":"Get Text Chat Session","operationId":"get_text_chat_session_api_v1_workflow__workflow_id__text_chat_sessions__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/WorkflowRunTextSessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/text-chat/sessions/{run_id}/messages":{"post":{"tags":["main","workflow-text-chat"],"summary":"Append Text Chat Message","operationId":"append_text_chat_message_api_v1_workflow__workflow_id__text_chat_sessions__run_id__messages_post","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"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppendTextChatMessageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunTextSessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/text-chat/sessions/{run_id}/rewind":{"post":{"tags":["main","workflow-text-chat"],"summary":"Rewind Text Chat Session","operationId":"rewind_text_chat_session_api_v1_workflow__workflow_id__text_chat_sessions__run_id__rewind_post","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"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewindTextChatSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunTextSessionResponse"}}}},"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"}}}}},"x-sdk-method":"create_tool","x-sdk-description":"Create a reusable tool for the authenticated organization."}},"/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/model-configurations/v2/defaults":{"get":{"tags":["main","organizations"],"summary":"Get Model Configuration V2 Defaults","operationId":"get_model_configuration_v2_defaults_api_v1_organizations_model_configurations_v2_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":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/model-configurations/v2":{"get":{"tags":["main","organizations"],"summary":"Get Model Configuration V2","operationId":"get_model_configuration_v2_api_v1_organizations_model_configurations_v2_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/OrganizationAIModelConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Save Model Configuration V2","operationId":"save_model_configuration_v2_api_v1_organizations_model_configurations_v2_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/OrganizationAIModelConfigurationV2"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationAIModelConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/model-configurations/v2/migration-preview":{"get":{"tags":["main","organizations"],"summary":"Preview Model Configuration V2 Migration","operationId":"preview_model_configuration_v2_migration_api_v1_organizations_model_configurations_v2_migration_preview_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":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/model-configurations/v2/migrate":{"post":{"tags":["main","organizations"],"summary":"Migrate Model Configuration V2","operationId":"migrate_model_configuration_v2_api_v1_organizations_model_configurations_v2_migrate_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-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/OrganizationAIModelConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/preferences":{"get":{"tags":["main","organizations"],"summary":"Get Preferences","operationId":"get_preferences_api_v1_organizations_preferences_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/OrganizationPreferences"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Save Preferences","operationId":"save_preferences_api_v1_organizations_preferences_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/OrganizationPreferences"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationPreferences"}}}},"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":"Fallback OPTIONS handler 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}":{"options":{"tags":["main"],"summary":"Options Embed Config","description":"Fallback OPTIONS handler for the embed config endpoint.\n\nBrowser preflights include Access-Control-Request-Method and are handled by\nPublicEmbedCORSMiddleware before global CORS. This keeps non-conformant\nOPTIONS requests on the same validation path.","operationId":"options_embed_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"}}}}}},"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"}}}}}}},"/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":"Fallback OPTIONS handler 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/agent/workflow/{workflow_uuid}":{"post":{"tags":["main"],"summary":"Initiate Call By Workflow Uuid","description":"Initiate a phone call against the published workflow identified by UUID.","operationId":"initiate_call_by_workflow_uuid_api_v1_public_agent_workflow__workflow_uuid__post","parameters":[{"name":"workflow_uuid","in":"path","required":true,"schema":{"type":"string","title":"Workflow 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/workflow/{workflow_uuid}":{"post":{"tags":["main"],"summary":"Initiate Call Test By Workflow Uuid","description":"Initiate a phone call against the latest draft of the workflow by UUID.","operationId":"initiate_call_test_by_workflow_uuid_api_v1_public_agent_test_workflow__workflow_uuid__post","parameters":[{"name":"workflow_uuid","in":"path","required":true,"schema":{"type":"string","title":"Workflow 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/folder/":{"get":{"tags":["main"],"summary":"List Folders","description":"List all folders in the authenticated user's organization.","operationId":"list_folders_api_v1_folder__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/FolderResponse"},"title":"Response List Folders Api V1 Folder Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main"],"summary":"Create Folder","description":"Create a new folder in the authenticated user's organization.","operationId":"create_folder_api_v1_folder__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/CreateFolderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/folder/{folder_id}":{"put":{"tags":["main"],"summary":"Rename Folder","description":"Rename a folder owned by the authenticated user's organization.","operationId":"rename_folder_api_v1_folder__folder_id__put","parameters":[{"name":"folder_id","in":"path","required":true,"schema":{"type":"integer","title":"Folder 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/UpdateFolderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Delete Folder","description":"Delete a folder. Member agents are moved to \"Uncategorized\", not deleted.","operationId":"delete_folder_api_v1_folder__folder_id__delete","parameters":[{"name":"folder_id","in":"path","required":true,"schema":{"type":"integer","title":"Folder 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":{"type":"boolean"},"title":"Response Delete Folder Api V1 Folder Folder Id Delete"}}}},"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."},"AWSBedrockLLMConfiguration":{"properties":{"provider":{"type":"string","const":"aws_bedrock","title":"Provider","default":"aws_bedrock"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Bedrock \u2014 authentication is via the AWS credentials above. Leave blank."},"model":{"type":"string","title":"Model","description":"Bedrock model ID \u2014 include the region inference-profile prefix (e.g. 'us.').","default":"us.amazon.nova-pro-v1:0","examples":["us.amazon.nova-pro-v1:0","us.amazon.nova-lite-v1:0","us.amazon.nova-micro-v1:0","us.anthropic.claude-sonnet-4-20250514-v1:0","us.anthropic.claude-3-5-sonnet-20241022-v2:0","us.anthropic.claude-haiku-4-5-20251001-v1:0"],"allow_custom_input":true},"aws_access_key":{"type":"string","title":"Aws Access Key","description":"AWS access key ID with bedrock:InvokeModel permission.","default":""},"aws_secret_key":{"type":"string","title":"Aws Secret Key","description":"AWS secret access key paired with the access key ID.","default":""},"aws_region":{"type":"string","title":"Aws Region","description":"AWS region where the Bedrock model is available.","default":"us-east-1"}},"type":"object","title":"AWS Bedrock"},"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"},"AppendTextChatMessageRequest":{"properties":{"text":{"type":"string","minLength":1,"title":"Text"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","required":["text"],"title":"AppendTextChatMessageRequest"},"AssemblyAISTTConfiguration":{"properties":{"provider":{"type":"string","const":"assemblyai","title":"Provider","default":"assemblyai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"AssemblyAI realtime STT model.","default":"u3-rt-pro","examples":["u3-rt-pro"]},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["en","es","de","fr","pt","it"]}},"type":"object","required":["api_key"],"title":"AssemblyAI"},"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"},"AzureLLMService":{"properties":{"provider":{"type":"string","const":"azure","title":"Provider","default":"azure"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure deployment name (not the upstream OpenAI model id).","default":"gpt-4.1-mini","examples":["gpt-4.1-mini"],"allow_custom_input":true},"endpoint":{"type":"string","title":"Endpoint","description":"Azure OpenAI resource endpoint (e.g. https://.openai.azure.com)."}},"type":"object","required":["api_key","endpoint"],"title":"Azure OpenAI"},"AzureOpenAIEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"azure","title":"Provider","default":"azure"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure OpenAI embedding deployment name. The deployment must return 1536-dimensional embeddings.","default":"text-embedding-3-small","examples":["text-embedding-3-small","text-embedding-ada-002"],"allow_custom_input":true},"endpoint":{"type":"string","title":"Endpoint","description":"Azure OpenAI resource endpoint (e.g. https://.openai.azure.com)."},"api_version":{"type":"string","title":"Api Version","description":"Azure OpenAI API version for embeddings.","default":"2024-02-15-preview"}},"type":"object","required":["api_key","endpoint"],"title":"Azure OpenAI"},"AzureRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"azure_realtime","title":"Provider","default":"azure_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure OpenAI realtime deployment name.","default":"gpt-4o-realtime-preview","examples":["gpt-4o-realtime-preview"],"allow_custom_input":true},"endpoint":{"type":"string","title":"Endpoint","description":"Azure OpenAI resource endpoint (e.g. https://.openai.azure.com)."},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"alloy","examples":["alloy","ash","ballad","coral","echo","sage","shimmer","verse"],"allow_custom_input":true},"api_version":{"type":"string","title":"Api Version","description":"Azure OpenAI API version.","default":"2025-04-01-preview","examples":["2025-04-01-preview","2024-10-01-preview","2024-12-17"]}},"type":"object","required":["api_key","endpoint"],"title":"Azure OpenAI Realtime","description":"Azure OpenAI Realtime API \u2014 low-latency speech-to-speech conversations.","provider_docs_url":"https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/realtime-audio-quickstart"},"AzureSpeechSTTConfiguration":{"properties":{"provider":{"type":"string","const":"azure_speech","title":"Provider","default":"azure_speech"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure Speech recognition model (use 'latest_long' for continuous recognition).","default":"latest_long","examples":["latest_long","latest_short"]},"region":{"type":"string","title":"Region","description":"Azure region for Speech Services (e.g. 'eastus', 'westeurope').","default":"eastus","examples":["eastus","eastus2","westus","westus2","westus3","centralus","northcentralus","southcentralus","westcentralus","westeurope","northeurope","uksouth","ukwest","francecentral","switzerlandnorth","germanywestcentral","norwayeast","australiaeast","eastasia","southeastasia","japaneast","japanwest","koreacentral","centralindia","southindia","brazilsouth"]},"language":{"type":"string","title":"Language","description":"BCP-47 language code for recognition.","default":"en-US","examples":["en-US","en-GB","en-AU","en-CA","en-IN","es-ES","es-MX","fr-FR","fr-CA","de-DE","it-IT","ja-JP","ko-KR","zh-CN","pt-BR","pt-PT","ru-RU","ar-SA","nl-NL","pl-PL","hi-IN"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Azure Speech Services","description":"Azure Cognitive Services Speech \u2014 TTS and STT via the Azure Speech SDK.","provider_docs_url":"https://learn.microsoft.com/en-us/azure/ai-services/speech-service/"},"AzureSpeechTTSConfiguration":{"properties":{"provider":{"type":"string","const":"azure_speech","title":"Provider","default":"azure_speech"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure Speech synthesis engine (neural voices only).","default":"neural","examples":["neural"]},"region":{"type":"string","title":"Region","description":"Azure region for Speech Services (e.g. 'eastus', 'westeurope').","default":"eastus","examples":["eastus","eastus2","westus","westus2","westus3","centralus","northcentralus","southcentralus","westcentralus","westeurope","northeurope","uksouth","ukwest","francecentral","switzerlandnorth","germanywestcentral","norwayeast","australiaeast","eastasia","southeastasia","japaneast","japanwest","koreacentral","centralindia","southindia","brazilsouth"]},"voice":{"type":"string","title":"Voice","description":"Azure Neural voice name (e.g. 'en-US-AriaNeural').","default":"en-US-AriaNeural","examples":["en-US-AriaNeural","en-US-GuyNeural","en-US-JennyNeural","en-US-DavisNeural","en-US-AmberNeural","en-US-AnaNeural","en-US-AshleyNeural","en-US-BrandonNeural","en-US-ChristopherNeural","en-US-ElizabethNeural","en-US-EricNeural","en-US-JacobNeural","en-US-MichelleNeural","en-US-MonicaNeural","en-US-NancyNeural","en-US-RogerNeural","en-US-SaraNeural","en-US-SteffanNeural","en-US-TonyNeural"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code for synthesis.","default":"en-US","examples":["en-US","en-GB","en-AU","en-CA","en-IN","es-ES","es-MX","fr-FR","fr-CA","de-DE","it-IT","ja-JP","ko-KR","zh-CN","zh-HK","zh-TW","pt-BR","pt-PT","ru-RU","ar-SA","nl-NL","pl-PL","sv-SE","hi-IN"],"allow_custom_input":true},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed multiplier (0.5 to 2.0).","default":1.0}},"type":"object","required":["api_key"],"title":"Azure Speech Services","description":"Azure Cognitive Services Speech \u2014 TTS and STT via the Azure Speech SDK.","provider_docs_url":"https://learn.microsoft.com/en-us/azure/ai-services/speech-service/"},"BYOKAIModelConfiguration":{"properties":{"mode":{"type":"string","enum":["pipeline","realtime"],"title":"Mode"},"pipeline":{"anyOf":[{"$ref":"#/components/schemas/BYOKPipelineAIModelConfiguration"},{"type":"null"}]},"realtime":{"anyOf":[{"$ref":"#/components/schemas/BYOKRealtimeAIModelConfiguration"},{"type":"null"}]}},"type":"object","required":["mode"],"title":"BYOKAIModelConfiguration"},"BYOKPipelineAIModelConfiguration":{"properties":{"llm":{"oneOf":[{"$ref":"#/components/schemas/OpenAILLMService"},{"$ref":"#/components/schemas/GoogleVertexLLMConfiguration"},{"$ref":"#/components/schemas/GroqLLMService"},{"$ref":"#/components/schemas/OpenRouterLLMConfiguration"},{"$ref":"#/components/schemas/GoogleLLMService"},{"$ref":"#/components/schemas/AzureLLMService"},{"$ref":"#/components/schemas/DograhLLMService"},{"$ref":"#/components/schemas/AWSBedrockLLMConfiguration"},{"$ref":"#/components/schemas/SpeachesLLMConfiguration"},{"$ref":"#/components/schemas/MiniMaxLLMConfiguration"},{"$ref":"#/components/schemas/SarvamLLMConfiguration"}],"title":"Llm","discriminator":{"propertyName":"provider","mapping":{"aws_bedrock":"#/components/schemas/AWSBedrockLLMConfiguration","azure":"#/components/schemas/AzureLLMService","dograh":"#/components/schemas/DograhLLMService","google":"#/components/schemas/GoogleLLMService","google_vertex":"#/components/schemas/GoogleVertexLLMConfiguration","groq":"#/components/schemas/GroqLLMService","minimax":"#/components/schemas/MiniMaxLLMConfiguration","openai":"#/components/schemas/OpenAILLMService","openrouter":"#/components/schemas/OpenRouterLLMConfiguration","sarvam":"#/components/schemas/SarvamLLMConfiguration","speaches":"#/components/schemas/SpeachesLLMConfiguration"}}},"tts":{"oneOf":[{"$ref":"#/components/schemas/DeepgramTTSConfiguration"},{"$ref":"#/components/schemas/GoogleTTSConfiguration"},{"$ref":"#/components/schemas/OpenAITTSService"},{"$ref":"#/components/schemas/ElevenlabsTTSConfiguration"},{"$ref":"#/components/schemas/CartesiaTTSConfiguration"},{"$ref":"#/components/schemas/DograhTTSService"},{"$ref":"#/components/schemas/SarvamTTSConfiguration"},{"$ref":"#/components/schemas/CambTTSConfiguration"},{"$ref":"#/components/schemas/RimeTTSConfiguration"},{"$ref":"#/components/schemas/SpeachesTTSConfiguration"},{"$ref":"#/components/schemas/MiniMaxTTSConfiguration"},{"$ref":"#/components/schemas/AzureSpeechTTSConfiguration"}],"title":"Tts","discriminator":{"propertyName":"provider","mapping":{"azure_speech":"#/components/schemas/AzureSpeechTTSConfiguration","camb":"#/components/schemas/CambTTSConfiguration","cartesia":"#/components/schemas/CartesiaTTSConfiguration","deepgram":"#/components/schemas/DeepgramTTSConfiguration","dograh":"#/components/schemas/DograhTTSService","elevenlabs":"#/components/schemas/ElevenlabsTTSConfiguration","google":"#/components/schemas/GoogleTTSConfiguration","minimax":"#/components/schemas/MiniMaxTTSConfiguration","openai":"#/components/schemas/OpenAITTSService","rime":"#/components/schemas/RimeTTSConfiguration","sarvam":"#/components/schemas/SarvamTTSConfiguration","speaches":"#/components/schemas/SpeachesTTSConfiguration"}}},"stt":{"oneOf":[{"$ref":"#/components/schemas/DeepgramSTTConfiguration"},{"$ref":"#/components/schemas/CartesiaSTTConfiguration"},{"$ref":"#/components/schemas/OpenAISTTConfiguration"},{"$ref":"#/components/schemas/GoogleSTTConfiguration"},{"$ref":"#/components/schemas/DograhSTTService"},{"$ref":"#/components/schemas/SpeechmaticsSTTConfiguration"},{"$ref":"#/components/schemas/SarvamSTTConfiguration"},{"$ref":"#/components/schemas/SpeachesSTTConfiguration"},{"$ref":"#/components/schemas/AssemblyAISTTConfiguration"},{"$ref":"#/components/schemas/GladiaSTTConfiguration"},{"$ref":"#/components/schemas/AzureSpeechSTTConfiguration"}],"title":"Stt","discriminator":{"propertyName":"provider","mapping":{"assemblyai":"#/components/schemas/AssemblyAISTTConfiguration","azure_speech":"#/components/schemas/AzureSpeechSTTConfiguration","cartesia":"#/components/schemas/CartesiaSTTConfiguration","deepgram":"#/components/schemas/DeepgramSTTConfiguration","dograh":"#/components/schemas/DograhSTTService","gladia":"#/components/schemas/GladiaSTTConfiguration","google":"#/components/schemas/GoogleSTTConfiguration","openai":"#/components/schemas/OpenAISTTConfiguration","sarvam":"#/components/schemas/SarvamSTTConfiguration","speaches":"#/components/schemas/SpeachesSTTConfiguration","speechmatics":"#/components/schemas/SpeechmaticsSTTConfiguration"}}},"embeddings":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/OpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/OpenRouterEmbeddingsConfiguration"},{"$ref":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/DograhEmbeddingsConfiguration"}],"discriminator":{"propertyName":"provider","mapping":{"azure":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration","dograh":"#/components/schemas/DograhEmbeddingsConfiguration","openai":"#/components/schemas/OpenAIEmbeddingsConfiguration","openrouter":"#/components/schemas/OpenRouterEmbeddingsConfiguration"}}},{"type":"null"}],"title":"Embeddings"}},"type":"object","required":["llm","tts","stt"],"title":"BYOKPipelineAIModelConfiguration"},"BYOKRealtimeAIModelConfiguration":{"properties":{"realtime":{"oneOf":[{"$ref":"#/components/schemas/OpenAIRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/GrokRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/UltravoxRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/GoogleRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/GoogleVertexRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/AzureRealtimeLLMConfiguration"}],"title":"Realtime","discriminator":{"propertyName":"provider","mapping":{"azure_realtime":"#/components/schemas/AzureRealtimeLLMConfiguration","google_realtime":"#/components/schemas/GoogleRealtimeLLMConfiguration","google_vertex_realtime":"#/components/schemas/GoogleVertexRealtimeLLMConfiguration","grok_realtime":"#/components/schemas/GrokRealtimeLLMConfiguration","openai_realtime":"#/components/schemas/OpenAIRealtimeLLMConfiguration","ultravox_realtime":"#/components/schemas/UltravoxRealtimeLLMConfiguration"}}},"llm":{"oneOf":[{"$ref":"#/components/schemas/OpenAILLMService"},{"$ref":"#/components/schemas/GoogleVertexLLMConfiguration"},{"$ref":"#/components/schemas/GroqLLMService"},{"$ref":"#/components/schemas/OpenRouterLLMConfiguration"},{"$ref":"#/components/schemas/GoogleLLMService"},{"$ref":"#/components/schemas/AzureLLMService"},{"$ref":"#/components/schemas/DograhLLMService"},{"$ref":"#/components/schemas/AWSBedrockLLMConfiguration"},{"$ref":"#/components/schemas/SpeachesLLMConfiguration"},{"$ref":"#/components/schemas/MiniMaxLLMConfiguration"},{"$ref":"#/components/schemas/SarvamLLMConfiguration"}],"title":"Llm","discriminator":{"propertyName":"provider","mapping":{"aws_bedrock":"#/components/schemas/AWSBedrockLLMConfiguration","azure":"#/components/schemas/AzureLLMService","dograh":"#/components/schemas/DograhLLMService","google":"#/components/schemas/GoogleLLMService","google_vertex":"#/components/schemas/GoogleVertexLLMConfiguration","groq":"#/components/schemas/GroqLLMService","minimax":"#/components/schemas/MiniMaxLLMConfiguration","openai":"#/components/schemas/OpenAILLMService","openrouter":"#/components/schemas/OpenRouterLLMConfiguration","sarvam":"#/components/schemas/SarvamLLMConfiguration","speaches":"#/components/schemas/SpeachesLLMConfiguration"}}},"embeddings":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/OpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/OpenRouterEmbeddingsConfiguration"},{"$ref":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/DograhEmbeddingsConfiguration"}],"discriminator":{"propertyName":"provider","mapping":{"azure":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration","dograh":"#/components/schemas/DograhEmbeddingsConfiguration","openai":"#/components/schemas/OpenAIEmbeddingsConfiguration","openrouter":"#/components/schemas/OpenRouterEmbeddingsConfiguration"}}},{"type":"null"}],"title":"Embeddings"}},"type":"object","required":["realtime","llm"],"title":"BYOKRealtimeAIModelConfiguration"},"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."},"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"},"CambTTSConfiguration":{"properties":{"provider":{"type":"string","const":"camb","title":"Provider","default":"camb"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Camb.ai TTS model.","default":"mars-flash","examples":["mars-flash","mars-pro","mars-instruct"]},"voice":{"type":"string","title":"Voice","description":"Camb.ai voice ID.","default":"147320"},"language":{"type":"string","title":"Language","description":"BCP-47 language code.","default":"en-us"}},"type":"object","required":["api_key"],"title":"Camb.ai"},"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"},"CartesiaSTTConfiguration":{"properties":{"provider":{"type":"string","const":"cartesia","title":"Provider","default":"cartesia"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Cartesia STT model.","default":"ink-whisper","examples":["ink-whisper"]}},"type":"object","required":["api_key"],"title":"Cartesia"},"CartesiaTTSConfiguration":{"properties":{"provider":{"type":"string","const":"cartesia","title":"Provider","default":"cartesia"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Cartesia TTS model.","default":"sonic-3","examples":["sonic-3"]},"voice":{"type":"string","title":"Voice","description":"Cartesia voice UUID from your Cartesia dashboard.","default":"3faa81ae-d3d8-4ab1-9e44-e50e46d33c30"},"speed":{"type":"number","maximum":1.5,"minimum":0.6,"title":"Speed","description":"Speed of the voice.","default":1.0},"volume":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Volume","description":"Volume multiplier for generated speech.","default":1.0}},"type":"object","required":["api_key"],"title":"Cartesia"},"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."},"CreateFolderRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"CreateFolderRequest"},"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"},"CreateTextChatSessionRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"annotations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annotations"}},"type":"object","title":"CreateTextChatSessionRequest"},"CreateToolRequest":{"properties":{"name":{"type":"string","maxLength":255,"title":"Name","description":"Display name for the tool.","llm_hint":"Use a concise action-oriented name; this influences the function name shown to the agent."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Description shown to the agent when deciding whether to call it.","llm_hint":"State exactly when the agent should call the tool and what result it gets."},"category":{"type":"string","enum":["http_api","end_call","transfer_call","calculator","native","integration","mcp"],"title":"Category","description":"Tool category. Must match definition.type.","default":"http_api"},"icon":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Icon","description":"Lucide icon identifier.","default":"globe"},"icon_color":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Icon Color","description":"Hex color for the tool icon.","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","description":"Typed tool 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 reusable 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"},"DeepgramSTTConfiguration":{"properties":{"provider":{"type":"string","const":"deepgram","title":"Provider","default":"deepgram"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Deepgram STT model.","default":"nova-3-general","examples":["nova-3-general","flux-general-en","flux-general-multi"]},"language":{"type":"string","title":"Language","description":"Language code; 'multi' enables auto-detect (Nova-3 only).","default":"multi","examples":["multi","ar","ar-AE","ar-SA","ar-QA","ar-KW","ar-SY","ar-LB","ar-PS","ar-JO","ar-EG","ar-SD","ar-TD","ar-MA","ar-DZ","ar-TN","ar-IQ","ar-IR","be","bn","bs","bg","ca","cs","da","da-DK","de","de-CH","el","en","en-US","en-AU","en-GB","en-IN","en-NZ","es","es-419","et","fa","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","kn","ko","ko-KR","lt","lv","mk","mr","ms","nl","nl-BE","no","pl","pt","pt-BR","pt-PT","ro","ru","sk","sl","sr","sv","sv-SE","ta","te","th","tl","tr","uk","ur","vi","zh-CN","zh-TW"],"model_options":{"flux-general-en":["en"],"nova-3-general":["multi","ar","ar-AE","ar-SA","ar-QA","ar-KW","ar-SY","ar-LB","ar-PS","ar-JO","ar-EG","ar-SD","ar-TD","ar-MA","ar-DZ","ar-TN","ar-IQ","ar-IR","be","bn","bs","bg","ca","cs","da","da-DK","de","de-CH","el","en","en-US","en-AU","en-GB","en-IN","en-NZ","es","es-419","et","fa","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","kn","ko","ko-KR","lt","lv","mk","mr","ms","nl","nl-BE","no","pl","pt","pt-BR","pt-PT","ro","ru","sk","sl","sr","sv","sv-SE","ta","te","th","tl","tr","uk","ur","vi","zh-CN","zh-TW"]}}},"type":"object","required":["api_key"],"title":"Deepgram"},"DeepgramTTSConfiguration":{"properties":{"provider":{"type":"string","const":"deepgram","title":"Provider","default":"deepgram"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"voice":{"type":"string","title":"Voice","description":"Deepgram voice ID (model is inferred from the 'aura-N' prefix).","default":"aura-2-helena-en"}},"type":"object","required":["api_key"],"title":"Deepgram"},"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."},"DograhEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh-managed embedding model.","default":"default","examples":["default"]}},"type":"object","required":["api_key"],"title":"Dograh"},"DograhLLMService":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh-hosted model tier.","default":"default","examples":["default","accurate","fast","lite","zen"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Dograh"},"DograhManagedAIModelConfiguration":{"properties":{"api_key":{"type":"string","title":"Api Key"},"voice":{"type":"string","title":"Voice","default":"default"},"speed":{"type":"number","title":"Speed","default":1.0},"language":{"type":"string","title":"Language","default":"multi"}},"type":"object","required":["api_key"],"title":"DograhManagedAIModelConfiguration"},"DograhSTTService":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh STT tier.","default":"default","examples":["default"]},"language":{"type":"string","title":"Language","description":"Language code; use 'multi' for auto-detect.","default":"multi","examples":["multi","ar","ar-AE","ar-SA","ar-QA","ar-KW","ar-SY","ar-LB","ar-PS","ar-JO","ar-EG","ar-SD","ar-TD","ar-MA","ar-DZ","ar-TN","ar-IQ","ar-IR","be","bn","bs","bg","ca","cs","da","da-DK","de","de-CH","el","en","en-US","en-AU","en-GB","en-IN","en-NZ","es","es-419","et","fa","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","kn","ko","ko-KR","lt","lv","mk","mr","ms","nl","nl-BE","no","pl","pt","pt-BR","pt-PT","ro","ru","sk","sl","sr","sv","sv-SE","ta","te","th","tl","tr","uk","ur","vi","zh-CN","zh-TW"]}},"type":"object","required":["api_key"],"title":"Dograh"},"DograhTTSService":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh TTS tier.","default":"default","examples":["default"]},"voice":{"type":"string","title":"Voice","description":"Voice preset.","default":"default"},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speed of the voice.","default":1.0}},"type":"object","required":["api_key"],"title":"Dograh"},"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"},"ElevenlabsTTSConfiguration":{"properties":{"provider":{"type":"string","const":"elevenlabs","title":"Provider","default":"elevenlabs"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"voice":{"type":"string","title":"Voice","description":"ElevenLabs voice ID from your Voice Library.","default":"21m00Tcm4TlvDq8ikWAM"},"speed":{"type":"number","maximum":2.0,"minimum":0.1,"title":"Speed","description":"Speed of the voice.","default":1.0},"model":{"type":"string","title":"Model","description":"ElevenLabs TTS model.","default":"eleven_flash_v2_5","examples":["eleven_flash_v2_5"]},"base_url":{"type":"string","title":"Base Url","description":"ElevenLabs API base URL. Override to use a Data Residency endpoint (e.g. https://api.eu.residency.elevenlabs.io) for GDPR / HIPAA / regional compliance.","default":"https://api.elevenlabs.io"}},"type":"object","required":["api_key"],"title":"ElevenLabs"},"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, the model 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 model 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"},"FolderResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","created_at"],"title":"FolderResponse"},"GladiaSTTConfiguration":{"properties":{"provider":{"type":"string","const":"gladia","title":"Provider","default":"gladia"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Gladia STT model.","default":"solaria-1","examples":["solaria-1"]},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["af","am","ar","as","az","ba","be","bg","bn","bo","br","bs","ca","cs","cy","da","de","el","en","es","et","eu","fa","fi","fo","fr","gl","gu","ha","haw","he","hi","hr","ht","hu","hy","id","is","it","ja","jw","ka","kk","km","kn","ko","la","lb","ln","lo","lt","lv","mg","mi","mk","ml","mn","mr","ms","mt","my","ne","nl","nn","no","oc","pa","pl","ps","pt","ro","ru","sa","sd","si","sk","sl","sn","so","sq","sr","su","sv","sw","ta","te","tg","th","tk","tl","tr","tt","uk","ur","uz","vi","wo","yi","yo","zh"]}},"type":"object","required":["api_key"],"title":"Gladia"},"GoogleLLMService":{"properties":{"provider":{"type":"string","const":"google","title":"Provider","default":"google"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Gemini model on Google AI Studio (not Vertex).","default":"gemini-2.0-flash","examples":["gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.5-flash","gemini-2.5-flash-lite","gemini-3.5-flash","gemini-3.5-flash-lite"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Google"},"GoogleRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"google_realtime","title":"Provider","default":"google_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Gemini Live model on Google AI Studio (not Vertex).","default":"gemini-3.1-flash-live-preview","examples":["gemini-3.1-flash-live-preview"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"Puck","examples":["Puck","Charon","Kore","Fenrir","Aoede"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["ar","bn","de","en","es","fr","gu","hi","id","it","ja","kn","ko","ml","mr","nl","pl","pt","ru","ta","te","th","tr","vi","zh"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Google Realtime"},"GoogleSTTConfiguration":{"properties":{"provider":{"type":"string","const":"google","title":"Provider","default":"google"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Google Cloud STT. Leave blank."},"model":{"type":"string","title":"Model","description":"Google Cloud Speech-to-Text V2 recognition model.","default":"latest_long","examples":["latest_long","latest_short","chirp_3"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"Primary BCP-47 language code for recognition.","default":"en-US","examples":["af-ZA","am-ET","ar-AE","ar-BH","ar-DZ","ar-EG","ar-IL","ar-IQ","ar-JO","ar-KW","ar-LB","ar-MA","ar-MR","ar-OM","ar-PS","ar-QA","ar-SA","ar-SY","ar-TN","ar-XA","ar-YE","as-IN","ast-ES","az-AZ","be-BY","bg-BG","bn-BD","bn-IN","bs-BA","ca-ES","ceb-PH","ckb-IQ","cmn-Hans-CN","cmn-Hant-TW","cs-CZ","cy-GB","da-DK","de-AT","de-CH","de-DE","el-GR","en-AU","en-GB","en-HK","en-IE","en-IN","en-NZ","en-PH","en-PK","en-SG","en-US","es-419","es-AR","es-BO","es-CL","es-CO","es-CR","es-DO","es-EC","es-ES","es-GT","es-HN","es-MX","es-NI","es-PA","es-PE","es-PR","es-SV","es-US","es-UY","es-VE","et-EE","eu-ES","fa-IR","ff-SN","fi-FI","fil-PH","fr-BE","fr-CA","fr-CH","fr-FR","ga-IE","gl-ES","gu-IN","ha-NG","hi-IN","hr-HR","hu-HU","hy-AM","id-ID","ig-NG","is-IS","it-CH","it-IT","iw-IL","ja-JP","jv-ID","ka-GE","kam-KE","kea-CV","kk-KZ","km-KH","kn-IN","ko-KR","ky-KG","lb-LU","lg-UG","ln-CD","lo-LA","lt-LT","luo-KE","lv-LV","mi-NZ","mk-MK","ml-IN","mn-MN","mr-IN","ms-MY","mt-MT","my-MM","ne-NP","nl-BE","nl-NL","no-NO","nso-ZA","ny-MW","oc-FR","om-ET","or-IN","pa-Guru-IN","pl-PL","ps-AF","pt-BR","pt-PT","ro-RO","ru-RU","rup-BG","rw-RW","sd-IN","si-LK","sk-SK","sl-SI","sn-ZW","so-SO","sq-AL","sr-RS","ss-Latn-ZA","st-ZA","su-ID","sv-SE","sw","sw-KE","ta-IN","te-IN","tg-TJ","th-TH","tn-Latn-ZA","tr-TR","ts-ZA","uk-UA","umb-AO","ur-PK","uz-UZ","ve-ZA","vi-VN","wo-SN","xh-ZA","yo-NG","yue-Hant-HK","zu-ZA"],"allow_custom_input":true,"docs_url":"https://docs.cloud.google.com/speech-to-text/docs/speech-to-text-supported-languages"},"location":{"type":"string","title":"Location","description":"Google Cloud Speech-to-Text region (for example 'global' or 'us-central1').","default":"global"},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire Google Cloud service-account JSON. If omitted, the server falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","title":"Google Cloud"},"GoogleTTSConfiguration":{"properties":{"provider":{"type":"string","const":"google","title":"Provider","default":"google"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Google Cloud TTS. Leave blank."},"model":{"type":"string","title":"Model","description":"Google Cloud low-latency TTS engine. Dograh maps this to Pipecat's streaming Google TTS service for Chirp 3 HD and Journey voices.","default":"chirp_3_hd","examples":["chirp_3_hd"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Google Cloud voice name. Use a Chirp 3 HD or Journey voice for streaming TTS.","default":"en-US-Chirp3-HD-Charon","examples":["en-US-Chirp3-HD-Charon"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code for synthesis.","default":"en-US","examples":["ar-XA","bn-IN","bg-BG","yue-HK","hr-HR","cs-CZ","da-DK","nl-BE","nl-NL","en-AU","en-IN","en-GB","en-US","et-EE","fi-FI","fr-CA","fr-FR","de-DE","el-GR","gu-IN","he-IL","hi-IN","hu-HU","id-ID","it-IT","ja-JP","kn-IN","ko-KR","lv-LV","lt-LT","ml-IN","cmn-CN","mr-IN","nb-NO","pl-PL","pt-BR","pa-IN","ro-RO","ru-RU","sr-RS","sk-SK","sl-SI","es-ES","es-US","sw-KE","sv-SE","ta-IN","te-IN","th-TH","tr-TR","uk-UA","ur-IN","vi-VN"],"allow_custom_input":true},"speed":{"type":"number","maximum":2.0,"minimum":0.25,"title":"Speed","description":"Speech speed multiplier for Google streaming TTS.","default":1.0},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Optional Google Cloud regional Text-to-Speech endpoint (for example 'us-central1'). Leave blank to use the default endpoint."},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire Google Cloud service-account JSON. If omitted, the server falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","title":"Google Cloud"},"GoogleVertexLLMConfiguration":{"properties":{"provider":{"type":"string","const":"google_vertex","title":"Provider","default":"google_vertex"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Vertex AI \u2014 authentication is via the service account in `credentials` (or ADC). Leave blank."},"model":{"type":"string","title":"Model","description":"Gemini model on Vertex AI.","default":"gemini-2.5-flash","examples":["gemini-2.5-flash","gemini-2.5-flash-lite","gemini-3.1-flash-lite","gemini-3.5-flash"],"allow_custom_input":true},"project_id":{"type":"string","title":"Project Id","description":"Google Cloud project ID for Vertex AI."},"location":{"type":"string","title":"Location","description":"GCP region for the Vertex AI endpoint (e.g. 'global').","default":"global"},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire service-account JSON file contents. If omitted, falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","required":["project_id"],"title":"Google Vertex"},"GoogleVertexRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"google_vertex_realtime","title":"Provider","default":"google_vertex_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Vertex AI \u2014 authentication is via the service account in `credentials` (or ADC). Leave blank."},"model":{"type":"string","title":"Model","description":"Vertex AI publisher/model identifier.","default":"google/gemini-live-2.5-flash-native-audio","examples":["google/gemini-live-2.5-flash-native-audio"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"Charon","examples":["Puck","Charon","Kore","Fenrir","Aoede"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code (e.g. 'en-US').","default":"en","examples":["ar","bn","de","en","es","fr","gu","hi","id","it","ja","kn","ko","ml","mr","nl","pl","pt","ru","ta","te","th","tr","vi","zh"],"allow_custom_input":true},"project_id":{"type":"string","title":"Project Id","description":"Google Cloud project ID for Vertex AI."},"location":{"type":"string","title":"Location","description":"GCP region for the Vertex AI endpoint (e.g. 'global').","default":"global"},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire service-account JSON file contents. If omitted, falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","required":["project_id"],"title":"Google Vertex Realtime"},"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."},"GrokRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"grok_realtime","title":"Provider","default":"grok_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Grok realtime voice-agent model.","default":"grok-voice-think-fast-1.0","examples":["grok-voice-think-fast-1.0"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"Ara","examples":["Ara","Rex","Sal","Eve","Leo"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Grok Realtime"},"GroqLLMService":{"properties":{"provider":{"type":"string","const":"groq","title":"Provider","default":"groq"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Groq-hosted model identifier.","default":"llama-3.3-70b-versatile","examples":["llama-3.3-70b-versatile","deepseek-r1-distill-llama-70b","qwen-qwq-32b","meta-llama/llama-4-scout-17b-16e-instruct","meta-llama/llama-4-maverick-17b-128e-instruct","gemma2-9b-it","llama-3.1-8b-instant","openai/gpt-oss-120b"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Groq"},"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","enum":["GET","POST","PUT","PATCH","DELETE"],"title":"Method","description":"HTTP method to use for the request.","llm_hint":"Use one of GET, POST, PUT, PATCH, DELETE."},"url":{"type":"string","title":"Url","description":"Target HTTP or HTTPS URL.","llm_hint":"Use the final endpoint URL. Authentication belongs in credential_uuid, not embedded in the URL."},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers","description":"Static headers to include with every request.","llm_hint":"Do not place secrets here. Store secrets in the UI credential manager and reference them with credential_uuid."},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to an external credential for request authentication.","llm_hint":"Use a credential_uuid returned by list_credentials. The MCP flow does not create credential secrets."},"parameters":{"anyOf":[{"items":{"$ref":"#/components/schemas/ToolParameter"},"type":"array"},{"type":"null"}],"title":"Parameters","description":"Parameters the model must provide when calling this tool."},"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","minimum":1.0},{"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."},"customMessageRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessagerecordingid","description":"Recording ID for an 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 use http:// or https://.","llm_hint":"Use the server's streamable HTTP MCP endpoint."},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to an external credential for MCP server auth.","llm_hint":"Use a credential_uuid returned by list_credentials. Credentials are created by the user in the UI."},"tools_filter":{"items":{"type":"string"},"type":"array","title":"Tools Filter","description":"Allowlist of MCP tool names to expose. Empty exposes all tools.","llm_hint":"Use exact MCP tool names from the remote server catalog when you need to restrict the exposed tools."},"timeout_secs":{"type":"integer","minimum":0.0,"title":"Timeout Secs","description":"Connection timeout in seconds.","default":30},"sse_read_timeout_secs":{"type":"integer","minimum":0.0,"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.","llm_hint":"Do not author this field; the server fills it."}},"type":"object","required":["url"],"title":"McpToolConfig","description":"Configuration for a customer MCP server 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."},"MiniMaxLLMConfiguration":{"properties":{"provider":{"type":"string","const":"minimax","title":"Provider","default":"minimax"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"MiniMax chat model.","default":"MiniMax-M2.7","examples":["MiniMax-M2.7","MiniMax-M2.7-highspeed"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"MiniMax OpenAI-compatible API endpoint.","default":"https://api.minimax.io/v1"},"temperature":{"type":"number","maximum":2.0,"exclusiveMinimum":0.0,"title":"Temperature","description":"Sampling temperature. MiniMax requires > 0.","default":1.0}},"type":"object","required":["api_key"],"title":"MiniMaxLLMConfiguration"},"MiniMaxTTSConfiguration":{"properties":{"provider":{"type":"string","const":"minimax","title":"Provider","default":"minimax"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"MiniMax TTS model.","default":"speech-2.8-hd","examples":["speech-2.8-hd","speech-2.8-turbo"]},"voice":{"type":"string","title":"Voice","description":"MiniMax voice ID.","default":"English_Graceful_Lady","examples":["English_Graceful_Lady","English_Insightful_Speaker","English_radiant_girl","English_Persuasive_Man","English_Lucky_Robot","English_expressive_narrator"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"MiniMax TTS API endpoint (must include the /v1/t2a_v2 path). Defaults to the global endpoint; override with https://api.minimaxi.chat/v1/t2a_v2 (mainland China) or https://api-uw.minimax.io/v1/t2a_v2 (US-West).","default":"https://api.minimax.io/v1/t2a_v2"},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed (0.5 to 2.0).","default":1.0},"group_id":{"type":"string","title":"Group Id","description":"MiniMax Group ID (found in your MiniMax dashboard under Account \u2192 Group)."}},"type":"object","required":["api_key","group_id"],"title":"MiniMaxTTSConfiguration"},"MoveWorkflowToFolderRequest":{"properties":{"folder_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Folder Id"}},"type":"object","title":"MoveWorkflowToFolderRequest","description":"Move a workflow into a folder, or to \"Uncategorized\" when null."},"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"},"OpenAIEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI embedding model.","default":"text-embedding-3-small","examples":["text-embedding-3-small"]}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenAILLMService":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI chat model to use.","default":"gpt-4.1","examples":["gpt-4.1","gpt-4.1-mini","gpt-4.1-nano","gpt-5","gpt-5-mini","gpt-5-nano","gpt-3.5-turbo"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"Override only if using an OpenAI-compatible API (e.g. local LLM, proxy).","default":"https://api.openai.com/v1"}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenAIRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"openai_realtime","title":"Provider","default":"openai_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI realtime (speech-to-speech) model.","default":"gpt-realtime-2","examples":["gpt-realtime-2"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"alloy","examples":["alloy","ash","ballad","coral","echo","sage","shimmer","verse"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"OpenAI Realtime"},"OpenAISTTConfiguration":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI transcription model.","default":"gpt-4o-transcribe","examples":["gpt-4o-transcribe"]},"base_url":{"type":"string","title":"Base Url","description":"Override only if using an OpenAI-compatible API (e.g. local STT, proxy).","default":"https://api.openai.com/v1"}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenAITTSService":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI TTS model.","default":"gpt-4o-mini-tts","examples":["gpt-4o-mini-tts"]},"voice":{"type":"string","title":"Voice","description":"OpenAI TTS voice name.","default":"alloy"},"base_url":{"type":"string","title":"Base Url","description":"Override only if using an OpenAI-compatible API (e.g. local TTS, proxy).","default":"https://api.openai.com/v1"}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenRouterEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"openrouter","title":"Provider","default":"openrouter"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenRouter-hosted embedding model slug.","default":"openai/text-embedding-3-small","examples":["openai/text-embedding-3-small"]},"base_url":{"type":"string","title":"Base Url","description":"Override only if proxying OpenRouter through your own gateway.","default":"https://openrouter.ai/api/v1"}},"type":"object","required":["api_key"],"title":"Open Router"},"OpenRouterLLMConfiguration":{"properties":{"provider":{"type":"string","const":"openrouter","title":"Provider","default":"openrouter"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenRouter model slug in 'vendor/model' form.","default":"openai/gpt-4.1","examples":["openai/gpt-4.1","openai/gpt-4.1-mini","anthropic/claude-sonnet-4","google/gemini-2.5-flash","google/gemini-2.0-flash","meta-llama/llama-3.3-70b-instruct","deepseek/deepseek-chat-v3-0324"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"Override only if proxying OpenRouter through your own gateway.","default":"https://openrouter.ai/api/v1"}},"type":"object","required":["api_key"],"title":"Open Router"},"OrganizationAIModelConfigurationResponse":{"properties":{"configuration":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Configuration"},"effective_configuration":{"additionalProperties":true,"type":"object","title":"Effective Configuration"},"source":{"type":"string","enum":["organization_v2","legacy_user_v1","empty"],"title":"Source"}},"type":"object","required":["configuration","effective_configuration","source"],"title":"OrganizationAIModelConfigurationResponse"},"OrganizationAIModelConfigurationV2":{"properties":{"version":{"type":"integer","const":2,"title":"Version","default":2},"mode":{"type":"string","enum":["dograh","byok"],"title":"Mode"},"dograh":{"anyOf":[{"$ref":"#/components/schemas/DograhManagedAIModelConfiguration"},{"type":"null"}]},"byok":{"anyOf":[{"$ref":"#/components/schemas/BYOKAIModelConfiguration"},{"type":"null"}]}},"type":"object","required":["mode"],"title":"OrganizationAIModelConfigurationV2"},"OrganizationPreferences":{"properties":{"test_phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Phone Number"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"}},"type":"object","title":"OrganizationPreferences"},"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 a key in the request body."},"type":{"type":"string","enum":["string","number","boolean","object","array"],"title":"Type","description":"JSON type for the resolved value.","llm_hint":"Allowed values are string, number, boolean, object, and array."},"value_template":{"type":"string","title":"Value Template","description":"Fixed value or template, e.g. {{initial_context.phone_number}}.","llm_hint":"Use {{initial_context.*}} for call-start context and {{gathered_context.*}} for values extracted during the call."},"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"},"RewindTextChatSessionRequest":{"properties":{"cursor_turn_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor Turn Id"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","title":"RewindTextChatSessionRequest"},"RimeTTSConfiguration":{"properties":{"provider":{"type":"string","const":"rime","title":"Provider","default":"rime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Rime TTS model.","default":"arcana","examples":["arcana","mistv3","mistv2","mist"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Rime voice ID.","default":"celeste"},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed multiplier.","default":1.0},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["en","de","fr","es","hi"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Rime"},"S3SignedUrlResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["url","expires_in"],"title":"S3SignedUrlResponse"},"SarvamLLMConfiguration":{"properties":{"provider":{"type":"string","const":"sarvam","title":"Provider","default":"sarvam"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Sarvam chat model. Use sarvam-30b for low-latency voice agents; sarvam-105b for complex multi-step reasoning.","default":"sarvam-30b","examples":["sarvam-30b","sarvam-105b"],"allow_custom_input":true},"temperature":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Temperature","description":"Sampling temperature. Sarvam recommends 0.5 for balanced conversational responses.","default":0.5}},"type":"object","required":["api_key"],"title":"Sarvam"},"SarvamSTTConfiguration":{"properties":{"provider":{"type":"string","const":"sarvam","title":"Provider","default":"sarvam"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Sarvam STT model. saarika:v2.5 transcribes in the spoken language; saaras:v3 is the recommended model with flexible output modes.","default":"saarika:v2.5","examples":["saarika:v2.5","saaras:v3"]},"language":{"type":"string","title":"Language","description":"BCP-47 language code. Use unknown for automatic language detection.","default":"unknown","examples":["unknown","hi-IN","bn-IN","gu-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","en-IN"],"model_options":{"saaras:v3":["unknown","hi-IN","bn-IN","gu-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","en-IN","as-IN","ur-IN","ne-IN","kok-IN","ks-IN","sd-IN","sa-IN","sat-IN","mni-IN","brx-IN","mai-IN","doi-IN"],"saarika:v2.5":["unknown","hi-IN","bn-IN","gu-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","en-IN"]}}},"type":"object","required":["api_key"],"title":"Sarvam"},"SarvamTTSConfiguration":{"properties":{"provider":{"type":"string","const":"sarvam","title":"Provider","default":"sarvam"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Sarvam TTS model (voice list depends on this).","default":"bulbul:v2","examples":["bulbul:v2","bulbul:v3"]},"voice":{"type":"string","title":"Voice","description":"Sarvam voice name; must match the selected model's voice list.","default":"anushka","examples":["anushka","manisha","vidya","arya","abhilash","karun","hitesh"],"model_options":{"bulbul:v2":["anushka","manisha","vidya","arya","abhilash","karun","hitesh"],"bulbul:v3":["shubh","aditya","ritu","priya","neha","rahul","pooja","rohan","simran","kavya","amit","dev","ishita","shreya","ratan","varun","manan","sumit","roopa","kabir","aayan","ashutosh","advait","amelia","sophia","anand","tanya","tarun","sunny","mani","gokul","vijay","shruti","suhani","mohit","kavitha","rehan","soham","rupali"]}},"language":{"type":"string","title":"Language","description":"BCP-47 Indian-language code (e.g. hi-IN, en-IN).","default":"hi-IN","examples":["bn-IN","en-IN","gu-IN","hi-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","as-IN"]}},"type":"object","required":["api_key"],"title":"Sarvam"},"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"},"SpeachesLLMConfiguration":{"properties":{"provider":{"type":"string","const":"speaches","title":"Provider","default":"speaches"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Usually not required for self-hosted endpoints. Leave blank unless your server enforces one."},"model":{"type":"string","title":"Model","description":"Model name as exposed by your OpenAI-compatible server.","default":"llama3","examples":["llama3","mistral","phi3","qwen2","gemma2","deepseek-r1"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"OpenAI-compatible endpoint (Ollama, vLLM, etc.).","default":"http://localhost:11434/v1"}},"type":"object","title":"Local Models (Speaches)","description":"Self-hosted OpenAI-compatible local models. See the Speaches project for setup and supported backends.","provider_docs_url":"https://github.com/speaches-ai/speaches"},"SpeachesSTTConfiguration":{"properties":{"provider":{"type":"string","const":"speaches","title":"Provider","default":"speaches"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Usually not required for self-hosted STT. Leave blank unless enforced."},"model":{"type":"string","title":"Model","description":"Whisper model identifier as served by your STT endpoint.","default":"Systran/faster-distil-whisper-small.en","examples":["Systran/faster-distil-whisper-small.en","Systran/faster-whisper-large-v3"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["en","ar","nl","fr","de","hi","it","pt","es"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"OpenAI-compatible STT endpoint (Speaches, etc.).","default":"http://localhost:8000/v1"}},"type":"object","title":"Local Models (Speaches)","description":"Self-hosted OpenAI-compatible local models. See the Speaches project for setup and supported backends.","provider_docs_url":"https://github.com/speaches-ai/speaches"},"SpeachesTTSConfiguration":{"properties":{"provider":{"type":"string","const":"speaches","title":"Provider","default":"speaches"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Usually not required for self-hosted TTS. Leave blank unless enforced."},"model":{"type":"string","title":"Model","description":"Model name as served by your TTS endpoint (e.g. Kokoro-FastAPI).","default":"kokoro","examples":["hexgrad/Kokoro-82M"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice ID for the TTS engine.","default":"af_heart","allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"OpenAI-compatible TTS endpoint (Kokoro-FastAPI, etc.).","default":"http://localhost:8000/v1"},"speed":{"type":"number","maximum":4.0,"minimum":0.25,"title":"Speed","description":"Speech speed (0.25 to 4.0).","default":1.0}},"type":"object","title":"Local Models (Speaches)","description":"Self-hosted OpenAI-compatible local models. See the Speaches project for setup and supported backends.","provider_docs_url":"https://github.com/speaches-ai/speaches"},"SpeechmaticsSTTConfiguration":{"properties":{"provider":{"type":"string","const":"speechmatics","title":"Provider","default":"speechmatics"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Speechmatics operating point: 'standard' or 'enhanced'.","default":"enhanced"},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["ar","ar_en","ba","eu","be","bn","bg","yue","ca","hr","cs","da","nl","en","eo","et","fi","fr","gl","de","el","he","hi","hu","id","ia","ga","it","ja","ko","lv","lt","ms","en_ms","mt","cmn","cmn_en","cmn_en_ms_ta","mr","mn","no","fa","pl","pt","ro","ru","sk","sl","es","sw","sv","tl","ta","en_ta","th","tr","uk","ur","ug","vi","cy"]}},"type":"object","required":["api_key"],"title":"Speechmatics"},"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 a key in the tool request body.","llm_hint":"Use a stable snake_case name the agent can naturally fill."},"type":{"type":"string","enum":["string","number","boolean","object","array"],"title":"Type","description":"JSON type for the parameter value.","llm_hint":"Allowed values are string, number, boolean, object, and array."},"description":{"type":"string","title":"Description","description":"Description shown to the model for this parameter.","llm_hint":"Write this as an instruction to the agent: what value to provide and when."},"required":{"type":"boolean","title":"Required","description":"Whether this parameter is required when the tool is called.","default":true}},"type":"object","required":["name","type","description"],"title":"ToolParameter","description":"A parameter that the tool accepts from the model at call time."},"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 reusable tool."},"TransferCallConfig":{"properties":{"destination":{"type":"string","title":"Destination","description":"Phone number or SIP endpoint to transfer the call to, e.g. +1234567890 or 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."},"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 seconds to wait for the destination to answer.","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."},"UltravoxRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"ultravox_realtime","title":"Provider","default":"ultravox_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Ultravox realtime voice-agent model.","default":"ultravox-v0.7","examples":["ultravox-v0.7","fixie-ai/ultravox"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Ultravox voice name or voice ID.","default":"Mark"}},"type":"object","required":["api_key"],"title":"Ultravox Realtime"},"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."},"UpdateFolderRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"UpdateFolderRequest"},"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 reusable 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"},"folder_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Folder Id"},"workflow_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Uuid"}},"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"},"transcript_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Public Url"},"recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Public Url"},"public_access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Access Token"},"cost_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cost Info"},"usage_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Usage 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"},"WorkflowRunTextSessionResponse":{"properties":{"workflow_run_id":{"type":"integer","title":"Workflow Run Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"name":{"type":"string","title":"Name"},"mode":{"type":"string","title":"Mode"},"state":{"type":"string","title":"State"},"is_completed":{"type":"boolean","title":"Is Completed"},"revision":{"type":"integer","title":"Revision"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"annotations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annotations"},"session_data":{"additionalProperties":true,"type":"object","title":"Session Data"},"checkpoint":{"additionalProperties":true,"type":"object","title":"Checkpoint"},"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":["workflow_run_id","workflow_id","name","mode","state","is_completed","revision","session_data","checkpoint","created_at"],"title":"WorkflowRunTextSessionResponse"},"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"},"recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Public Url"},"transcript_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Public Url"},"public_access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Access Token"},"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"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode"},"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"}}],"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"}}],"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"}}],"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"}}],"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"}}],"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"}}],"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}/folder":{"put":{"tags":["main"],"summary":"Move Workflow To Folder","description":"Move a workflow into a folder, or to \"Uncategorized\" (folder_id=null).\n\nValidates that the target folder belongs to the caller's organization \u2014\nthe FK alone proves the folder exists, not that the caller may use it.","operationId":"move_workflow_to_folder_api_v1_workflow__workflow_id__folder_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/MoveWorkflowToFolderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowListResponse"}}}},"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/workflow/{workflow_id}/text-chat/sessions":{"post":{"tags":["main","workflow-text-chat"],"summary":"Create Text Chat Session","operationId":"create_text_chat_session_api_v1_workflow__workflow_id__text_chat_sessions_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/CreateTextChatSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunTextSessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/text-chat/sessions/{run_id}":{"get":{"tags":["main","workflow-text-chat"],"summary":"Get Text Chat Session","operationId":"get_text_chat_session_api_v1_workflow__workflow_id__text_chat_sessions__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/WorkflowRunTextSessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/text-chat/sessions/{run_id}/messages":{"post":{"tags":["main","workflow-text-chat"],"summary":"Append Text Chat Message","operationId":"append_text_chat_message_api_v1_workflow__workflow_id__text_chat_sessions__run_id__messages_post","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"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppendTextChatMessageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunTextSessionResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/workflow/{workflow_id}/text-chat/sessions/{run_id}/rewind":{"post":{"tags":["main","workflow-text-chat"],"summary":"Rewind Text Chat Session","operationId":"rewind_text_chat_session_api_v1_workflow__workflow_id__text_chat_sessions__run_id__rewind_post","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"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RewindTextChatSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowRunTextSessionResponse"}}}},"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/onboarding-state":{"get":{"tags":["main"],"summary":"Get User Onboarding State","operationId":"get_user_onboarding_state_api_v1_user_onboarding_state_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/OnboardingState"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main"],"summary":"Update User Onboarding State","operationId":"update_user_onboarding_state_api_v1_user_onboarding_state_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/OnboardingStateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingState"}}}},"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"}}}}},"x-sdk-method":"create_tool","x-sdk-description":"Create a reusable tool for the authenticated organization."}},"/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/context":{"get":{"tags":["main","organizations"],"summary":"Get Current Organization Context","description":"Return organization-scoped configuration signals owned by Dograh.","operationId":"get_current_organization_context_api_v1_organizations_context_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/OrganizationContextResponse"}}}},"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/model-configurations/v2/defaults":{"get":{"tags":["main","organizations"],"summary":"Get Model Configuration V2 Defaults","operationId":"get_model_configuration_v2_defaults_api_v1_organizations_model_configurations_v2_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":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/model-configurations/v2":{"get":{"tags":["main","organizations"],"summary":"Get Model Configuration V2","operationId":"get_model_configuration_v2_api_v1_organizations_model_configurations_v2_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/OrganizationAIModelConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Save Model Configuration V2","operationId":"save_model_configuration_v2_api_v1_organizations_model_configurations_v2_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/OrganizationAIModelConfigurationV2"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationAIModelConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/model-configurations/v2/migration-preview":{"get":{"tags":["main","organizations"],"summary":"Preview Model Configuration V2 Migration","operationId":"preview_model_configuration_v2_migration_api_v1_organizations_model_configurations_v2_migration_preview_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":{}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/model-configurations/v2/migrate":{"post":{"tags":["main","organizations"],"summary":"Migrate Model Configuration V2","operationId":"migrate_model_configuration_v2_api_v1_organizations_model_configurations_v2_migrate_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-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/OrganizationAIModelConfigurationResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/preferences":{"get":{"tags":["main","organizations"],"summary":"Get Preferences","operationId":"get_preferences_api_v1_organizations_preferences_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/OrganizationPreferences"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["main","organizations"],"summary":"Save Preferences","operationId":"save_preferences_api_v1_organizations_preferences_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/OrganizationPreferences"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationPreferences"}}}},"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* Known org-scoped keys (for example ``campaigns/{org_id}/...`` and\n ``knowledge_base/{org_id}/...``) are authorized by matching the org_id\n against the requesting user's 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 reporting-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/billing/credits":{"get":{"tags":["main"],"summary":"Get Billing Credits","description":"Return legacy MPS credits or paginated v2 billing ledger details for the org.","operationId":"get_billing_credits_api_v1_organizations_billing_credits_get","parameters":[{"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":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-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/MPSBillingCreditsResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/organizations/usage/mps-credits/purchase-url":{"post":{"tags":["main"],"summary":"Create Mps Credit Purchase Url","description":"Create a checkout URL for organizations using Dograh-managed MPS v2.","operationId":"create_mps_credit_purchase_url_api_v1_organizations_usage_mps_credits_purchase_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"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MPSCreditPurchaseUrlResponse"}}}},"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":"Fallback OPTIONS handler 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}":{"options":{"tags":["main"],"summary":"Options Embed Config","description":"Fallback OPTIONS handler for the embed config endpoint.\n\nBrowser preflights include Access-Control-Request-Method and are handled by\nPublicEmbedCORSMiddleware before global CORS. This keeps non-conformant\nOPTIONS requests on the same validation path.","operationId":"options_embed_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"}}}}}},"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"}}}}}}},"/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":"Fallback OPTIONS handler 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/agent/workflow/{workflow_uuid}":{"post":{"tags":["main"],"summary":"Initiate Call By Workflow Uuid","description":"Initiate a phone call against the published workflow identified by UUID.","operationId":"initiate_call_by_workflow_uuid_api_v1_public_agent_workflow__workflow_uuid__post","parameters":[{"name":"workflow_uuid","in":"path","required":true,"schema":{"type":"string","title":"Workflow 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/workflow/{workflow_uuid}":{"post":{"tags":["main"],"summary":"Initiate Call Test By Workflow Uuid","description":"Initiate a phone call against the latest draft of the workflow by UUID.","operationId":"initiate_call_test_by_workflow_uuid_api_v1_public_agent_test_workflow__workflow_uuid__post","parameters":[{"name":"workflow_uuid","in":"path","required":true,"schema":{"type":"string","title":"Workflow 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\", \"transcript\",\n \"user_recording\", or \"bot_recording\"\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 400: If artifact type is unsupported\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":{"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/folder/":{"get":{"tags":["main"],"summary":"List Folders","description":"List all folders in the authenticated user's organization.","operationId":"list_folders_api_v1_folder__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/FolderResponse"},"title":"Response List Folders Api V1 Folder Get"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["main"],"summary":"Create Folder","description":"Create a new folder in the authenticated user's organization.","operationId":"create_folder_api_v1_folder__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/CreateFolderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/folder/{folder_id}":{"put":{"tags":["main"],"summary":"Rename Folder","description":"Rename a folder owned by the authenticated user's organization.","operationId":"rename_folder_api_v1_folder__folder_id__put","parameters":[{"name":"folder_id","in":"path","required":true,"schema":{"type":"integer","title":"Folder 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/UpdateFolderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderResponse"}}}},"404":{"description":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["main"],"summary":"Delete Folder","description":"Delete a folder. Member agents are moved to \"Uncategorized\", not deleted.","operationId":"delete_folder_api_v1_folder__folder_id__delete","parameters":[{"name":"folder_id","in":"path","required":true,"schema":{"type":"integer","title":"Folder 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":{"type":"boolean"},"title":"Response Delete Folder Api V1 Folder Folder Id Delete"}}}},"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."},"AWSBedrockLLMConfiguration":{"properties":{"provider":{"type":"string","const":"aws_bedrock","title":"Provider","default":"aws_bedrock"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Bedrock \u2014 authentication is via the AWS credentials above. Leave blank."},"model":{"type":"string","title":"Model","description":"Bedrock model ID \u2014 include the region inference-profile prefix (e.g. 'us.').","default":"us.amazon.nova-pro-v1:0","examples":["us.amazon.nova-pro-v1:0","us.amazon.nova-lite-v1:0","us.amazon.nova-micro-v1:0","us.anthropic.claude-sonnet-4-20250514-v1:0","us.anthropic.claude-3-5-sonnet-20241022-v2:0","us.anthropic.claude-haiku-4-5-20251001-v1:0"],"allow_custom_input":true},"aws_access_key":{"type":"string","title":"Aws Access Key","description":"AWS access key ID with bedrock:InvokeModel permission.","default":""},"aws_secret_key":{"type":"string","title":"Aws Secret Key","description":"AWS secret access key paired with the access key ID.","default":""},"aws_region":{"type":"string","title":"Aws Region","description":"AWS region where the Bedrock model is available.","default":"us-east-1"}},"type":"object","title":"AWS Bedrock"},"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"},"AppendTextChatMessageRequest":{"properties":{"text":{"type":"string","minLength":1,"title":"Text"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","required":["text"],"title":"AppendTextChatMessageRequest"},"AssemblyAISTTConfiguration":{"properties":{"provider":{"type":"string","const":"assemblyai","title":"Provider","default":"assemblyai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"AssemblyAI realtime STT model.","default":"u3-rt-pro","examples":["u3-rt-pro"]},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["en","es","de","fr","pt","it"]}},"type":"object","required":["api_key"],"title":"AssemblyAI"},"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"},"AzureLLMService":{"properties":{"provider":{"type":"string","const":"azure","title":"Provider","default":"azure"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure deployment name (not the upstream OpenAI model id).","default":"gpt-4.1-mini","examples":["gpt-4.1-mini"],"allow_custom_input":true},"endpoint":{"type":"string","title":"Endpoint","description":"Azure OpenAI resource endpoint (e.g. https://.openai.azure.com)."}},"type":"object","required":["api_key","endpoint"],"title":"Azure OpenAI"},"AzureOpenAIEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"azure","title":"Provider","default":"azure"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure OpenAI embedding deployment name. The deployment must return 1536-dimensional embeddings.","default":"text-embedding-3-small","examples":["text-embedding-3-small","text-embedding-ada-002"],"allow_custom_input":true},"endpoint":{"type":"string","title":"Endpoint","description":"Azure OpenAI resource endpoint (e.g. https://.openai.azure.com)."},"api_version":{"type":"string","title":"Api Version","description":"Azure OpenAI API version for embeddings.","default":"2024-02-15-preview"}},"type":"object","required":["api_key","endpoint"],"title":"Azure OpenAI"},"AzureRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"azure_realtime","title":"Provider","default":"azure_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure OpenAI realtime deployment name.","default":"gpt-4o-realtime-preview","examples":["gpt-4o-realtime-preview"],"allow_custom_input":true},"endpoint":{"type":"string","title":"Endpoint","description":"Azure OpenAI resource endpoint (e.g. https://.openai.azure.com)."},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"alloy","examples":["alloy","ash","ballad","coral","echo","sage","shimmer","verse"],"allow_custom_input":true},"api_version":{"type":"string","title":"Api Version","description":"Azure OpenAI API version.","default":"2025-04-01-preview","examples":["2025-04-01-preview","2024-10-01-preview","2024-12-17"]}},"type":"object","required":["api_key","endpoint"],"title":"Azure OpenAI Realtime","description":"Azure OpenAI Realtime API \u2014 low-latency speech-to-speech conversations.","provider_docs_url":"https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/realtime-audio-quickstart"},"AzureSpeechSTTConfiguration":{"properties":{"provider":{"type":"string","const":"azure_speech","title":"Provider","default":"azure_speech"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure Speech recognition model (use 'latest_long' for continuous recognition).","default":"latest_long","examples":["latest_long","latest_short"]},"region":{"type":"string","title":"Region","description":"Azure region for Speech Services (e.g. 'eastus', 'westeurope').","default":"eastus","examples":["eastus","eastus2","westus","westus2","westus3","centralus","northcentralus","southcentralus","westcentralus","westeurope","northeurope","uksouth","ukwest","francecentral","switzerlandnorth","germanywestcentral","norwayeast","australiaeast","eastasia","southeastasia","japaneast","japanwest","koreacentral","centralindia","southindia","brazilsouth"]},"language":{"type":"string","title":"Language","description":"BCP-47 language code for recognition.","default":"en-US","examples":["en-US","en-GB","en-AU","en-CA","en-IN","es-ES","es-MX","fr-FR","fr-CA","de-DE","it-IT","ja-JP","ko-KR","zh-CN","pt-BR","pt-PT","ru-RU","ar-SA","nl-NL","pl-PL","hi-IN"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Azure Speech Services","description":"Azure Cognitive Services Speech \u2014 TTS and STT via the Azure Speech SDK.","provider_docs_url":"https://learn.microsoft.com/en-us/azure/ai-services/speech-service/"},"AzureSpeechTTSConfiguration":{"properties":{"provider":{"type":"string","const":"azure_speech","title":"Provider","default":"azure_speech"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Azure Speech synthesis engine (neural voices only).","default":"neural","examples":["neural"]},"region":{"type":"string","title":"Region","description":"Azure region for Speech Services (e.g. 'eastus', 'westeurope').","default":"eastus","examples":["eastus","eastus2","westus","westus2","westus3","centralus","northcentralus","southcentralus","westcentralus","westeurope","northeurope","uksouth","ukwest","francecentral","switzerlandnorth","germanywestcentral","norwayeast","australiaeast","eastasia","southeastasia","japaneast","japanwest","koreacentral","centralindia","southindia","brazilsouth"]},"voice":{"type":"string","title":"Voice","description":"Azure Neural voice name (e.g. 'en-US-AriaNeural').","default":"en-US-AriaNeural","examples":["en-US-AriaNeural","en-US-GuyNeural","en-US-JennyNeural","en-US-DavisNeural","en-US-AmberNeural","en-US-AnaNeural","en-US-AshleyNeural","en-US-BrandonNeural","en-US-ChristopherNeural","en-US-ElizabethNeural","en-US-EricNeural","en-US-JacobNeural","en-US-MichelleNeural","en-US-MonicaNeural","en-US-NancyNeural","en-US-RogerNeural","en-US-SaraNeural","en-US-SteffanNeural","en-US-TonyNeural"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code for synthesis.","default":"en-US","examples":["en-US","en-GB","en-AU","en-CA","en-IN","es-ES","es-MX","fr-FR","fr-CA","de-DE","it-IT","ja-JP","ko-KR","zh-CN","zh-HK","zh-TW","pt-BR","pt-PT","ru-RU","ar-SA","nl-NL","pl-PL","sv-SE","hi-IN"],"allow_custom_input":true},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed multiplier (0.5 to 2.0).","default":1.0}},"type":"object","required":["api_key"],"title":"Azure Speech Services","description":"Azure Cognitive Services Speech \u2014 TTS and STT via the Azure Speech SDK.","provider_docs_url":"https://learn.microsoft.com/en-us/azure/ai-services/speech-service/"},"BYOKAIModelConfiguration":{"properties":{"mode":{"type":"string","enum":["pipeline","realtime"],"title":"Mode"},"pipeline":{"anyOf":[{"$ref":"#/components/schemas/BYOKPipelineAIModelConfiguration"},{"type":"null"}]},"realtime":{"anyOf":[{"$ref":"#/components/schemas/BYOKRealtimeAIModelConfiguration"},{"type":"null"}]}},"type":"object","required":["mode"],"title":"BYOKAIModelConfiguration"},"BYOKPipelineAIModelConfiguration":{"properties":{"llm":{"oneOf":[{"$ref":"#/components/schemas/OpenAILLMService"},{"$ref":"#/components/schemas/GoogleVertexLLMConfiguration"},{"$ref":"#/components/schemas/GroqLLMService"},{"$ref":"#/components/schemas/OpenRouterLLMConfiguration"},{"$ref":"#/components/schemas/GoogleLLMService"},{"$ref":"#/components/schemas/AzureLLMService"},{"$ref":"#/components/schemas/DograhLLMService"},{"$ref":"#/components/schemas/AWSBedrockLLMConfiguration"},{"$ref":"#/components/schemas/SpeachesLLMConfiguration"},{"$ref":"#/components/schemas/HuggingFaceLLMConfiguration"},{"$ref":"#/components/schemas/MiniMaxLLMConfiguration"},{"$ref":"#/components/schemas/SarvamLLMConfiguration"}],"title":"Llm","discriminator":{"propertyName":"provider","mapping":{"aws_bedrock":"#/components/schemas/AWSBedrockLLMConfiguration","azure":"#/components/schemas/AzureLLMService","dograh":"#/components/schemas/DograhLLMService","google":"#/components/schemas/GoogleLLMService","google_vertex":"#/components/schemas/GoogleVertexLLMConfiguration","groq":"#/components/schemas/GroqLLMService","huggingface":"#/components/schemas/HuggingFaceLLMConfiguration","minimax":"#/components/schemas/MiniMaxLLMConfiguration","openai":"#/components/schemas/OpenAILLMService","openrouter":"#/components/schemas/OpenRouterLLMConfiguration","sarvam":"#/components/schemas/SarvamLLMConfiguration","speaches":"#/components/schemas/SpeachesLLMConfiguration"}}},"tts":{"oneOf":[{"$ref":"#/components/schemas/DeepgramTTSConfiguration"},{"$ref":"#/components/schemas/GoogleTTSConfiguration"},{"$ref":"#/components/schemas/OpenAITTSService"},{"$ref":"#/components/schemas/ElevenlabsTTSConfiguration"},{"$ref":"#/components/schemas/CartesiaTTSConfiguration"},{"$ref":"#/components/schemas/InworldTTSConfiguration"},{"$ref":"#/components/schemas/DograhTTSService"},{"$ref":"#/components/schemas/SarvamTTSConfiguration"},{"$ref":"#/components/schemas/CambTTSConfiguration"},{"$ref":"#/components/schemas/RimeTTSConfiguration"},{"$ref":"#/components/schemas/SpeachesTTSConfiguration"},{"$ref":"#/components/schemas/MiniMaxTTSConfiguration"},{"$ref":"#/components/schemas/AzureSpeechTTSConfiguration"},{"$ref":"#/components/schemas/SmallestAITTSConfiguration"}],"title":"Tts","discriminator":{"propertyName":"provider","mapping":{"azure_speech":"#/components/schemas/AzureSpeechTTSConfiguration","camb":"#/components/schemas/CambTTSConfiguration","cartesia":"#/components/schemas/CartesiaTTSConfiguration","deepgram":"#/components/schemas/DeepgramTTSConfiguration","dograh":"#/components/schemas/DograhTTSService","elevenlabs":"#/components/schemas/ElevenlabsTTSConfiguration","google":"#/components/schemas/GoogleTTSConfiguration","inworld":"#/components/schemas/InworldTTSConfiguration","minimax":"#/components/schemas/MiniMaxTTSConfiguration","openai":"#/components/schemas/OpenAITTSService","rime":"#/components/schemas/RimeTTSConfiguration","sarvam":"#/components/schemas/SarvamTTSConfiguration","smallest":"#/components/schemas/SmallestAITTSConfiguration","speaches":"#/components/schemas/SpeachesTTSConfiguration"}}},"stt":{"oneOf":[{"$ref":"#/components/schemas/DeepgramSTTConfiguration"},{"$ref":"#/components/schemas/CartesiaSTTConfiguration"},{"$ref":"#/components/schemas/OpenAISTTConfiguration"},{"$ref":"#/components/schemas/GoogleSTTConfiguration"},{"$ref":"#/components/schemas/DograhSTTService"},{"$ref":"#/components/schemas/SpeechmaticsSTTConfiguration"},{"$ref":"#/components/schemas/SarvamSTTConfiguration"},{"$ref":"#/components/schemas/SpeachesSTTConfiguration"},{"$ref":"#/components/schemas/HuggingFaceSTTConfiguration"},{"$ref":"#/components/schemas/AssemblyAISTTConfiguration"},{"$ref":"#/components/schemas/GladiaSTTConfiguration"},{"$ref":"#/components/schemas/AzureSpeechSTTConfiguration"},{"$ref":"#/components/schemas/SmallestAISTTConfiguration"}],"title":"Stt","discriminator":{"propertyName":"provider","mapping":{"assemblyai":"#/components/schemas/AssemblyAISTTConfiguration","azure_speech":"#/components/schemas/AzureSpeechSTTConfiguration","cartesia":"#/components/schemas/CartesiaSTTConfiguration","deepgram":"#/components/schemas/DeepgramSTTConfiguration","dograh":"#/components/schemas/DograhSTTService","gladia":"#/components/schemas/GladiaSTTConfiguration","google":"#/components/schemas/GoogleSTTConfiguration","huggingface":"#/components/schemas/HuggingFaceSTTConfiguration","openai":"#/components/schemas/OpenAISTTConfiguration","sarvam":"#/components/schemas/SarvamSTTConfiguration","smallest":"#/components/schemas/SmallestAISTTConfiguration","speaches":"#/components/schemas/SpeachesSTTConfiguration","speechmatics":"#/components/schemas/SpeechmaticsSTTConfiguration"}}},"embeddings":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/OpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/OpenRouterEmbeddingsConfiguration"},{"$ref":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/DograhEmbeddingsConfiguration"}],"discriminator":{"propertyName":"provider","mapping":{"azure":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration","dograh":"#/components/schemas/DograhEmbeddingsConfiguration","openai":"#/components/schemas/OpenAIEmbeddingsConfiguration","openrouter":"#/components/schemas/OpenRouterEmbeddingsConfiguration"}}},{"type":"null"}],"title":"Embeddings"}},"type":"object","required":["llm","tts","stt"],"title":"BYOKPipelineAIModelConfiguration"},"BYOKRealtimeAIModelConfiguration":{"properties":{"realtime":{"oneOf":[{"$ref":"#/components/schemas/OpenAIRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/GrokRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/UltravoxRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/GoogleRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/GoogleVertexRealtimeLLMConfiguration"},{"$ref":"#/components/schemas/AzureRealtimeLLMConfiguration"}],"title":"Realtime","discriminator":{"propertyName":"provider","mapping":{"azure_realtime":"#/components/schemas/AzureRealtimeLLMConfiguration","google_realtime":"#/components/schemas/GoogleRealtimeLLMConfiguration","google_vertex_realtime":"#/components/schemas/GoogleVertexRealtimeLLMConfiguration","grok_realtime":"#/components/schemas/GrokRealtimeLLMConfiguration","openai_realtime":"#/components/schemas/OpenAIRealtimeLLMConfiguration","ultravox_realtime":"#/components/schemas/UltravoxRealtimeLLMConfiguration"}}},"llm":{"oneOf":[{"$ref":"#/components/schemas/OpenAILLMService"},{"$ref":"#/components/schemas/GoogleVertexLLMConfiguration"},{"$ref":"#/components/schemas/GroqLLMService"},{"$ref":"#/components/schemas/OpenRouterLLMConfiguration"},{"$ref":"#/components/schemas/GoogleLLMService"},{"$ref":"#/components/schemas/AzureLLMService"},{"$ref":"#/components/schemas/DograhLLMService"},{"$ref":"#/components/schemas/AWSBedrockLLMConfiguration"},{"$ref":"#/components/schemas/SpeachesLLMConfiguration"},{"$ref":"#/components/schemas/HuggingFaceLLMConfiguration"},{"$ref":"#/components/schemas/MiniMaxLLMConfiguration"},{"$ref":"#/components/schemas/SarvamLLMConfiguration"}],"title":"Llm","discriminator":{"propertyName":"provider","mapping":{"aws_bedrock":"#/components/schemas/AWSBedrockLLMConfiguration","azure":"#/components/schemas/AzureLLMService","dograh":"#/components/schemas/DograhLLMService","google":"#/components/schemas/GoogleLLMService","google_vertex":"#/components/schemas/GoogleVertexLLMConfiguration","groq":"#/components/schemas/GroqLLMService","huggingface":"#/components/schemas/HuggingFaceLLMConfiguration","minimax":"#/components/schemas/MiniMaxLLMConfiguration","openai":"#/components/schemas/OpenAILLMService","openrouter":"#/components/schemas/OpenRouterLLMConfiguration","sarvam":"#/components/schemas/SarvamLLMConfiguration","speaches":"#/components/schemas/SpeachesLLMConfiguration"}}},"embeddings":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/OpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/OpenRouterEmbeddingsConfiguration"},{"$ref":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration"},{"$ref":"#/components/schemas/DograhEmbeddingsConfiguration"}],"discriminator":{"propertyName":"provider","mapping":{"azure":"#/components/schemas/AzureOpenAIEmbeddingsConfiguration","dograh":"#/components/schemas/DograhEmbeddingsConfiguration","openai":"#/components/schemas/OpenAIEmbeddingsConfiguration","openrouter":"#/components/schemas/OpenRouterEmbeddingsConfiguration"}}},{"type":"null"}],"title":"Embeddings"}},"type":"object","required":["realtime","llm"],"title":"BYOKRealtimeAIModelConfiguration"},"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."},"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"},"CambTTSConfiguration":{"properties":{"provider":{"type":"string","const":"camb","title":"Provider","default":"camb"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Camb.ai TTS model.","default":"mars-flash","examples":["mars-flash","mars-pro","mars-instruct"]},"voice":{"type":"string","title":"Voice","description":"Camb.ai voice ID.","default":"147320"},"language":{"type":"string","title":"Language","description":"BCP-47 language code.","default":"en-us"}},"type":"object","required":["api_key"],"title":"Camb.ai"},"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"},"CartesiaSTTConfiguration":{"properties":{"provider":{"type":"string","const":"cartesia","title":"Provider","default":"cartesia"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Cartesia STT model.","default":"ink-whisper","examples":["ink-whisper"]}},"type":"object","required":["api_key"],"title":"Cartesia"},"CartesiaTTSConfiguration":{"properties":{"provider":{"type":"string","const":"cartesia","title":"Provider","default":"cartesia"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Cartesia TTS model.","default":"sonic-3.5","examples":["sonic-3.5","sonic-3"]},"voice":{"type":"string","title":"Voice","description":"Cartesia voice UUID from your Cartesia dashboard.","default":"3faa81ae-d3d8-4ab1-9e44-e50e46d33c30"},"speed":{"type":"number","maximum":1.5,"minimum":0.6,"title":"Speed","description":"Speed of the voice.","default":1.0},"volume":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Volume","description":"Volume multiplier for generated speech.","default":1.0},"language":{"type":"string","title":"Language","description":"Cartesia language code for TTS synthesis (e.g. 'en', 'tr', 'fr', 'de').","default":"en","allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Cartesia"},"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."},"CreateFolderRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"CreateFolderRequest"},"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"},"CreateTextChatSessionRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"annotations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annotations"}},"type":"object","title":"CreateTextChatSessionRequest"},"CreateToolRequest":{"properties":{"name":{"type":"string","maxLength":255,"title":"Name","description":"Display name for the tool.","llm_hint":"Use a concise action-oriented name; this influences the function name shown to the agent."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Description shown to the agent when deciding whether to call it.","llm_hint":"State exactly when the agent should call the tool and what result it gets."},"category":{"type":"string","enum":["http_api","end_call","transfer_call","calculator","native","integration","mcp"],"title":"Category","description":"Tool category. Must match definition.type.","default":"http_api"},"icon":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Icon","description":"Lucide icon identifier.","default":"globe"},"icon_color":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Icon Color","description":"Hex color for the tool icon.","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","description":"Typed tool 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 reusable 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"},"total_duration_seconds":{"type":"integer","title":"Total Duration Seconds"},"used_amount_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Used 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","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"},"DeepgramSTTConfiguration":{"properties":{"provider":{"type":"string","const":"deepgram","title":"Provider","default":"deepgram"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Deepgram STT model.","default":"nova-3-general","examples":["nova-3-general","flux-general-en","flux-general-multi"]},"language":{"type":"string","title":"Language","description":"Language code. 'multi' enables Nova-3 auto-detect and omits language hints for Flux multilingual auto-detect.","default":"multi","examples":["multi","ar","ar-AE","ar-SA","ar-QA","ar-KW","ar-SY","ar-LB","ar-PS","ar-JO","ar-EG","ar-SD","ar-TD","ar-MA","ar-DZ","ar-TN","ar-IQ","ar-IR","be","bn","bs","bg","ca","cs","da","da-DK","de","de-CH","el","en","en-US","en-AU","en-GB","en-IN","en-NZ","es","es-419","et","fa","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","kn","ko","ko-KR","lt","lv","mk","mr","ms","nl","nl-BE","no","pl","pt","pt-BR","pt-PT","ro","ru","sk","sl","sr","sv","sv-SE","ta","te","th","tl","tr","uk","ur","vi","zh-CN","zh-TW"],"model_options":{"flux-general-en":["en"],"flux-general-multi":["multi","de","en","es","fr","hi","it","ja","nl","pt","ru"],"nova-3-general":["multi","ar","ar-AE","ar-SA","ar-QA","ar-KW","ar-SY","ar-LB","ar-PS","ar-JO","ar-EG","ar-SD","ar-TD","ar-MA","ar-DZ","ar-TN","ar-IQ","ar-IR","be","bn","bs","bg","ca","cs","da","da-DK","de","de-CH","el","en","en-US","en-AU","en-GB","en-IN","en-NZ","es","es-419","et","fa","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","kn","ko","ko-KR","lt","lv","mk","mr","ms","nl","nl-BE","no","pl","pt","pt-BR","pt-PT","ro","ru","sk","sl","sr","sv","sv-SE","ta","te","th","tl","tr","uk","ur","vi","zh-CN","zh-TW"]}}},"type":"object","required":["api_key"],"title":"Deepgram"},"DeepgramTTSConfiguration":{"properties":{"provider":{"type":"string","const":"deepgram","title":"Provider","default":"deepgram"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"voice":{"type":"string","title":"Voice","description":"Deepgram voice ID (model is inferred from the 'aura-N' prefix).","default":"aura-2-helena-en"}},"type":"object","required":["api_key"],"title":"Deepgram"},"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."},"DograhEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh-managed embedding model.","default":"default","examples":["default"]}},"type":"object","required":["api_key"],"title":"Dograh"},"DograhLLMService":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh-hosted model tier.","default":"default","examples":["default","accurate","fast","lite","zen"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Dograh"},"DograhManagedAIModelConfiguration":{"properties":{"api_key":{"type":"string","title":"Api Key"},"voice":{"type":"string","title":"Voice","default":"default"},"speed":{"type":"number","title":"Speed","default":1.0},"language":{"type":"string","title":"Language","default":"multi"}},"type":"object","required":["api_key"],"title":"DograhManagedAIModelConfiguration"},"DograhSTTService":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh STT tier.","default":"default","examples":["default"]},"language":{"type":"string","title":"Language","description":"Language code; use 'multi' for auto-detect.","default":"multi","examples":["multi","ar","ar-AE","ar-SA","ar-QA","ar-KW","ar-SY","ar-LB","ar-PS","ar-JO","ar-EG","ar-SD","ar-TD","ar-MA","ar-DZ","ar-TN","ar-IQ","ar-IR","be","bn","bs","bg","ca","cs","da","da-DK","de","de-CH","el","en","en-US","en-AU","en-GB","en-IN","en-NZ","es","es-419","et","fa","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","kn","ko","ko-KR","lt","lv","mk","mr","ms","nl","nl-BE","no","pl","pt","pt-BR","pt-PT","ro","ru","sk","sl","sr","sv","sv-SE","ta","te","th","tl","tr","uk","ur","vi","zh-CN","zh-TW"]}},"type":"object","required":["api_key"],"title":"Dograh"},"DograhTTSService":{"properties":{"provider":{"type":"string","const":"dograh","title":"Provider","default":"dograh"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Dograh TTS tier.","default":"default","examples":["default"]},"voice":{"type":"string","title":"Voice","description":"Voice preset.","default":"default","allow_custom_input":true},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speed of the voice.","default":1.0}},"type":"object","required":["api_key"],"title":"Dograh"},"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"},"ElevenlabsTTSConfiguration":{"properties":{"provider":{"type":"string","const":"elevenlabs","title":"Provider","default":"elevenlabs"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"voice":{"type":"string","title":"Voice","description":"ElevenLabs voice ID from your Voice Library.","default":"21m00Tcm4TlvDq8ikWAM"},"speed":{"type":"number","maximum":2.0,"minimum":0.1,"title":"Speed","description":"Speed of the voice.","default":1.0},"model":{"type":"string","title":"Model","description":"ElevenLabs TTS model.","default":"eleven_flash_v2_5","examples":["eleven_flash_v2_5"]},"base_url":{"type":"string","title":"Base Url","description":"ElevenLabs API base URL. Override to use a Data Residency endpoint (e.g. https://api.eu.residency.elevenlabs.io) for GDPR / HIPAA / regional compliance.","default":"https://api.elevenlabs.io"}},"type":"object","required":["api_key"],"title":"ElevenLabs"},"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, the model 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 model 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"},"FolderResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","created_at"],"title":"FolderResponse"},"GladiaSTTConfiguration":{"properties":{"provider":{"type":"string","const":"gladia","title":"Provider","default":"gladia"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Gladia STT model.","default":"solaria-1","examples":["solaria-1"]},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["af","am","ar","as","az","ba","be","bg","bn","bo","br","bs","ca","cs","cy","da","de","el","en","es","et","eu","fa","fi","fo","fr","gl","gu","ha","haw","he","hi","hr","ht","hu","hy","id","is","it","ja","jw","ka","kk","km","kn","ko","la","lb","ln","lo","lt","lv","mg","mi","mk","ml","mn","mr","ms","mt","my","ne","nl","nn","no","oc","pa","pl","ps","pt","ro","ru","sa","sd","si","sk","sl","sn","so","sq","sr","su","sv","sw","ta","te","tg","th","tk","tl","tr","tt","uk","ur","uz","vi","wo","yi","yo","zh"]}},"type":"object","required":["api_key"],"title":"Gladia"},"GoogleLLMService":{"properties":{"provider":{"type":"string","const":"google","title":"Provider","default":"google"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Gemini model on Google AI Studio (not Vertex).","default":"gemini-2.0-flash","examples":["gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.5-flash","gemini-2.5-flash-lite","gemini-3.5-flash","gemini-3.5-flash-lite"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Google"},"GoogleRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"google_realtime","title":"Provider","default":"google_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Gemini Live model on Google AI Studio (not Vertex).","default":"gemini-3.1-flash-live-preview","examples":["gemini-3.1-flash-live-preview"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"Puck","examples":["Puck","Charon","Kore","Fenrir","Aoede"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["ar","bn","de","en","es","fr","gu","hi","id","it","ja","kn","ko","ml","mr","nl","pl","pt","ru","ta","te","th","tr","vi","zh"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Google Realtime"},"GoogleSTTConfiguration":{"properties":{"provider":{"type":"string","const":"google","title":"Provider","default":"google"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Google Cloud STT. Leave blank."},"model":{"type":"string","title":"Model","description":"Google Cloud Speech-to-Text V2 recognition model.","default":"latest_long","examples":["latest_long","latest_short","chirp_3"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"Primary BCP-47 language code for recognition.","default":"en-US","examples":["af-ZA","am-ET","ar-AE","ar-BH","ar-DZ","ar-EG","ar-IL","ar-IQ","ar-JO","ar-KW","ar-LB","ar-MA","ar-MR","ar-OM","ar-PS","ar-QA","ar-SA","ar-SY","ar-TN","ar-XA","ar-YE","as-IN","ast-ES","az-AZ","be-BY","bg-BG","bn-BD","bn-IN","bs-BA","ca-ES","ceb-PH","ckb-IQ","cmn-Hans-CN","cmn-Hant-TW","cs-CZ","cy-GB","da-DK","de-AT","de-CH","de-DE","el-GR","en-AU","en-GB","en-HK","en-IE","en-IN","en-NZ","en-PH","en-PK","en-SG","en-US","es-419","es-AR","es-BO","es-CL","es-CO","es-CR","es-DO","es-EC","es-ES","es-GT","es-HN","es-MX","es-NI","es-PA","es-PE","es-PR","es-SV","es-US","es-UY","es-VE","et-EE","eu-ES","fa-IR","ff-SN","fi-FI","fil-PH","fr-BE","fr-CA","fr-CH","fr-FR","ga-IE","gl-ES","gu-IN","ha-NG","hi-IN","hr-HR","hu-HU","hy-AM","id-ID","ig-NG","is-IS","it-CH","it-IT","iw-IL","ja-JP","jv-ID","ka-GE","kam-KE","kea-CV","kk-KZ","km-KH","kn-IN","ko-KR","ky-KG","lb-LU","lg-UG","ln-CD","lo-LA","lt-LT","luo-KE","lv-LV","mi-NZ","mk-MK","ml-IN","mn-MN","mr-IN","ms-MY","mt-MT","my-MM","ne-NP","nl-BE","nl-NL","no-NO","nso-ZA","ny-MW","oc-FR","om-ET","or-IN","pa-Guru-IN","pl-PL","ps-AF","pt-BR","pt-PT","ro-RO","ru-RU","rup-BG","rw-RW","sd-IN","si-LK","sk-SK","sl-SI","sn-ZW","so-SO","sq-AL","sr-RS","ss-Latn-ZA","st-ZA","su-ID","sv-SE","sw","sw-KE","ta-IN","te-IN","tg-TJ","th-TH","tn-Latn-ZA","tr-TR","ts-ZA","uk-UA","umb-AO","ur-PK","uz-UZ","ve-ZA","vi-VN","wo-SN","xh-ZA","yo-NG","yue-Hant-HK","zu-ZA"],"allow_custom_input":true,"docs_url":"https://docs.cloud.google.com/speech-to-text/docs/speech-to-text-supported-languages"},"location":{"type":"string","title":"Location","description":"Google Cloud Speech-to-Text region (for example 'global' or 'us-central1').","default":"global"},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire Google Cloud service-account JSON. If omitted, the server falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","title":"Google Cloud"},"GoogleTTSConfiguration":{"properties":{"provider":{"type":"string","const":"google","title":"Provider","default":"google"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Google Cloud TTS. Leave blank."},"model":{"type":"string","title":"Model","description":"Google Cloud low-latency TTS engine. Dograh maps this to Pipecat's streaming Google TTS service for Chirp 3 HD and Journey voices.","default":"chirp_3_hd","examples":["chirp_3_hd"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Google Cloud voice name. Use a Chirp 3 HD or Journey voice for streaming TTS.","default":"en-US-Chirp3-HD-Charon","examples":["en-US-Chirp3-HD-Charon"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code for synthesis.","default":"en-US","examples":["ar-XA","bn-IN","bg-BG","yue-HK","hr-HR","cs-CZ","da-DK","nl-BE","nl-NL","en-AU","en-IN","en-GB","en-US","et-EE","fi-FI","fr-CA","fr-FR","de-DE","el-GR","gu-IN","he-IL","hi-IN","hu-HU","id-ID","it-IT","ja-JP","kn-IN","ko-KR","lv-LV","lt-LT","ml-IN","cmn-CN","mr-IN","nb-NO","pl-PL","pt-BR","pa-IN","ro-RO","ru-RU","sr-RS","sk-SK","sl-SI","es-ES","es-US","sw-KE","sv-SE","ta-IN","te-IN","th-TH","tr-TR","uk-UA","ur-IN","vi-VN"],"allow_custom_input":true},"speed":{"type":"number","maximum":2.0,"minimum":0.25,"title":"Speed","description":"Speech speed multiplier for Google streaming TTS.","default":1.0},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Optional Google Cloud regional Text-to-Speech endpoint (for example 'us-central1'). Leave blank to use the default endpoint."},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire Google Cloud service-account JSON. If omitted, the server falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","title":"Google Cloud"},"GoogleVertexLLMConfiguration":{"properties":{"provider":{"type":"string","const":"google_vertex","title":"Provider","default":"google_vertex"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Vertex AI \u2014 authentication is via the service account in `credentials` (or ADC). Leave blank."},"model":{"type":"string","title":"Model","description":"Gemini model on Vertex AI.","default":"gemini-2.5-flash","examples":["gemini-2.5-flash","gemini-2.5-flash-lite","gemini-3.1-flash-lite","gemini-3.5-flash"],"allow_custom_input":true},"project_id":{"type":"string","title":"Project Id","description":"Google Cloud project ID for Vertex AI."},"location":{"type":"string","title":"Location","description":"GCP region for the Vertex AI endpoint (e.g. 'global').","default":"global"},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire service-account JSON file contents. If omitted, falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","required":["project_id"],"title":"Google Vertex"},"GoogleVertexRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"google_vertex_realtime","title":"Provider","default":"google_vertex_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Not used for Vertex AI \u2014 authentication is via the service account in `credentials` (or ADC). Leave blank."},"model":{"type":"string","title":"Model","description":"Vertex AI publisher/model identifier.","default":"google/gemini-live-2.5-flash-native-audio","examples":["google/gemini-live-2.5-flash-native-audio"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"Charon","examples":["Puck","Charon","Kore","Fenrir","Aoede"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code (e.g. 'en-US').","default":"en","examples":["ar","bn","de","en","es","fr","gu","hi","id","it","ja","kn","ko","ml","mr","nl","pl","pt","ru","ta","te","th","tr","vi","zh"],"allow_custom_input":true},"project_id":{"type":"string","title":"Project Id","description":"Google Cloud project ID for Vertex AI."},"location":{"type":"string","title":"Location","description":"GCP region for the Vertex AI endpoint (e.g. 'global').","default":"global"},"credentials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credentials","description":"Paste the entire service-account JSON file contents. If omitted, falls back to Application Default Credentials (ADC).","multiline":true}},"type":"object","required":["project_id"],"title":"Google Vertex Realtime"},"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"},"min_instances":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Instances"},"max_instances":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Instances"}},"additionalProperties":false,"type":"object","title":"GraphConstraints","description":"Per-node-type graph rules. WorkflowGraph enforces these at validation."},"GrokRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"grok_realtime","title":"Provider","default":"grok_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Grok realtime voice-agent model.","default":"grok-voice-think-fast-1.0","examples":["grok-voice-think-fast-1.0"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"Ara","examples":["Ara","Rex","Sal","Eve","Leo"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Grok Realtime"},"GroqLLMService":{"properties":{"provider":{"type":"string","const":"groq","title":"Provider","default":"groq"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Groq-hosted model identifier.","default":"llama-3.3-70b-versatile","examples":["llama-3.3-70b-versatile","deepseek-r1-distill-llama-70b","qwen-qwq-32b","meta-llama/llama-4-scout-17b-16e-instruct","meta-llama/llama-4-maverick-17b-128e-instruct","gemma2-9b-it","llama-3.1-8b-instant","openai/gpt-oss-120b"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Groq"},"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"},"stack_project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stack Project Id"},"stack_publishable_client_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stack Publishable Client Key"}},"type":"object","required":["status","version","backend_api_endpoint","deployment_mode","auth_provider","turn_enabled","force_turn_relay"],"title":"HealthResponse"},"HttpApiConfig":{"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"],"title":"Method","description":"HTTP method to use for the request.","llm_hint":"Use one of GET, POST, PUT, PATCH, DELETE."},"url":{"type":"string","title":"Url","description":"Target HTTP or HTTPS URL.","llm_hint":"Use the final endpoint URL. Authentication belongs in credential_uuid, not embedded in the URL."},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers","description":"Static headers to include with every request.","llm_hint":"Do not place secrets here. Store secrets in the UI credential manager and reference them with credential_uuid."},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to an external credential for request authentication.","llm_hint":"Use a credential_uuid returned by list_credentials. The MCP flow does not create credential secrets."},"parameters":{"anyOf":[{"items":{"$ref":"#/components/schemas/ToolParameter"},"type":"array"},{"type":"null"}],"title":"Parameters","description":"Parameters the model must provide when calling this tool."},"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","minimum":1.0},{"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."},"customMessageRecordingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custommessagerecordingid","description":"Recording ID for an 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."},"HuggingFaceLLMConfiguration":{"properties":{"provider":{"type":"string","const":"huggingface","title":"Provider","default":"huggingface"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Hugging Face chat-completion model identifier, optionally with provider suffix.","default":"openai/gpt-oss-120b:cerebras","examples":["openai/gpt-oss-120b:cerebras","deepseek-ai/DeepSeek-R1:fastest","Qwen/Qwen3-Coder-480B-A35B-Instruct:fastest"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"Hugging Face OpenAI-compatible chat-completions router base URL.","default":"https://router.huggingface.co/v1"},"bill_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bill To","description":"Optional Hugging Face organization or user to bill using X-HF-Bill-To."}},"type":"object","required":["api_key"],"title":"Hugging Face","description":"Hosted Hugging Face Inference Providers API for usage-based inference.","provider_docs_url":"https://huggingface.co/docs/inference-providers/en/index"},"HuggingFaceSTTConfiguration":{"properties":{"provider":{"type":"string","const":"huggingface","title":"Provider","default":"huggingface"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Hugging Face ASR model identifier served through Inference Providers.","default":"openai/whisper-large-v3-turbo","examples":["openai/whisper-large-v3-turbo","openai/whisper-large-v3"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"Hugging Face Inference Providers router base URL.","default":"https://router.huggingface.co/hf-inference"},"bill_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bill To","description":"Optional Hugging Face organization or user to bill using X-HF-Bill-To."},"return_timestamps":{"type":"boolean","title":"Return Timestamps","description":"Request timestamp chunks when supported by the selected provider/model.","default":false}},"type":"object","required":["api_key"],"title":"Hugging Face","description":"Hosted Hugging Face Inference Providers API for usage-based inference.","provider_docs_url":"https://huggingface.co/docs/inference-providers/en/index"},"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"},"InworldTTSConfiguration":{"properties":{"provider":{"type":"string","const":"inworld","title":"Provider","default":"inworld"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Inworld TTS model.","default":"inworld-tts-2","examples":["inworld-tts-2"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Inworld voice ID. Use Ashley for the default warm English voice, or a workspace voice ID for a cloned/custom voice.","default":"Ashley","examples":["Ashley"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"BCP-47 language code for synthesis.","default":"en-US","examples":["en-US"],"allow_custom_input":true},"speed":{"type":"number","maximum":4.0,"minimum":0.25,"title":"Speed","description":"Speech speed multiplier.","default":1.0},"delivery_mode":{"type":"string","enum":["STABLE","BALANCED","CREATIVE"],"title":"Delivery Mode","description":"Controls stability versus expressiveness for inworld-tts-2 (STABLE, BALANCED, or CREATIVE).","default":"BALANCED"}},"type":"object","required":["api_key"],"title":"Inworld","description":"Inworld AI streaming text-to-speech with built-in and cloned voices. Defaults to the Ashley system voice on inworld-tts-2.","provider_docs_url":"https://docs.inworld.ai/tts/tts"},"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"},"MPSBillingAccountResponse":{"properties":{"id":{"type":"integer","title":"Id"},"organization_id":{"type":"integer","title":"Organization Id"},"billing_mode":{"type":"string","title":"Billing Mode"},"cached_balance_credits":{"type":"number","title":"Cached Balance Credits"},"currency":{"type":"string","title":"Currency"}},"type":"object","required":["id","organization_id","billing_mode","cached_balance_credits","currency"],"title":"MPSBillingAccountResponse"},"MPSBillingCreditsResponse":{"properties":{"billing_version":{"type":"string","enum":["legacy","v2"],"title":"Billing Version"},"total_credits_used":{"type":"number","title":"Total Credits Used","default":0.0},"remaining_credits":{"type":"number","title":"Remaining Credits","default":0.0},"total_quota":{"type":"number","title":"Total Quota","default":0.0},"account":{"anyOf":[{"$ref":"#/components/schemas/MPSBillingAccountResponse"},{"type":"null"}]},"ledger_entries":{"items":{"$ref":"#/components/schemas/MPSCreditLedgerEntryResponse"},"type":"array","title":"Ledger Entries"},"total_count":{"type":"integer","title":"Total Count","default":0},"page":{"type":"integer","title":"Page","default":1},"limit":{"type":"integer","title":"Limit","default":50},"total_pages":{"type":"integer","title":"Total Pages","default":0}},"type":"object","required":["billing_version"],"title":"MPSBillingCreditsResponse"},"MPSCreditLedgerEntryResponse":{"properties":{"id":{"type":"integer","title":"Id"},"entry_type":{"type":"string","title":"Entry Type"},"origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin"},"credits_delta":{"type":"number","title":"Credits Delta"},"balance_after":{"type":"number","title":"Balance After"},"amount_minor":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amount Minor"},"amount_currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Amount Currency"},"payment_order_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payment Order Id"},"metric_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric Code"},"correlation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correlation Id"},"aggregation_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aggregation Key"},"usage_event_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Usage Event Id"},"workflow_run_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Run Id"},"workflow_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Workflow Id"},"billable_quantity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Billable Quantity"},"quantity_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quantity Unit"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","entry_type","credits_delta","balance_after","created_at"],"title":"MPSCreditLedgerEntryResponse"},"MPSCreditPurchaseUrlResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url"}},"type":"object","required":["checkout_url"],"title":"MPSCreditPurchaseUrlResponse"},"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 use http:// or https://.","llm_hint":"Use the server's streamable HTTP MCP endpoint."},"credential_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Uuid","description":"Reference to an external credential for MCP server auth.","llm_hint":"Use a credential_uuid returned by list_credentials. Credentials are created by the user in the UI."},"tools_filter":{"items":{"type":"string"},"type":"array","title":"Tools Filter","description":"Allowlist of MCP tool names to expose. Empty exposes all tools.","llm_hint":"Use exact MCP tool names from the remote server catalog when you need to restrict the exposed tools."},"timeout_secs":{"type":"integer","minimum":0.0,"title":"Timeout Secs","description":"Connection timeout in seconds.","default":30},"sse_read_timeout_secs":{"type":"integer","minimum":0.0,"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.","llm_hint":"Do not author this field; the server fills it."}},"type":"object","required":["url"],"title":"McpToolConfig","description":"Configuration for a customer MCP server 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."},"MiniMaxLLMConfiguration":{"properties":{"provider":{"type":"string","const":"minimax","title":"Provider","default":"minimax"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"MiniMax chat model.","default":"MiniMax-M2.7","examples":["MiniMax-M2.7","MiniMax-M2.7-highspeed"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"MiniMax OpenAI-compatible API endpoint.","default":"https://api.minimax.io/v1"},"temperature":{"type":"number","maximum":2.0,"exclusiveMinimum":0.0,"title":"Temperature","description":"Sampling temperature. MiniMax requires > 0.","default":1.0}},"type":"object","required":["api_key"],"title":"MiniMaxLLMConfiguration"},"MiniMaxTTSConfiguration":{"properties":{"provider":{"type":"string","const":"minimax","title":"Provider","default":"minimax"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"MiniMax TTS model.","default":"speech-2.8-hd","examples":["speech-2.8-hd","speech-2.8-turbo"]},"voice":{"type":"string","title":"Voice","description":"MiniMax voice ID.","default":"English_Graceful_Lady","examples":["English_Graceful_Lady","English_Insightful_Speaker","English_radiant_girl","English_Persuasive_Man","English_Lucky_Robot","English_expressive_narrator"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"MiniMax TTS API endpoint (must include the /v1/t2a_v2 path). Defaults to the global endpoint; override with https://api.minimaxi.chat/v1/t2a_v2 (mainland China) or https://api-uw.minimax.io/v1/t2a_v2 (US-West).","default":"https://api.minimax.io/v1/t2a_v2"},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed (0.5 to 2.0).","default":1.0},"group_id":{"type":"string","title":"Group Id","description":"MiniMax Group ID (found in your MiniMax dashboard under Account \u2192 Group)."}},"type":"object","required":["api_key","group_id"],"title":"MiniMaxTTSConfiguration"},"MoveWorkflowToFolderRequest":{"properties":{"folder_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Folder Id"}},"type":"object","title":"MoveWorkflowToFolderRequest","description":"Move a workflow into a folder, or to \"Uncategorized\" when null."},"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"},"OnboardingState":{"properties":{"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"skipped":{"type":"boolean","title":"Skipped","default":false},"seen_tooltips":{"items":{"type":"string"},"type":"array","title":"Seen Tooltips"},"completed_actions":{"items":{"type":"string"},"type":"array","title":"Completed Actions"}},"type":"object","title":"OnboardingState","description":"Per-user onboarding state, stored under UserConfigurationKey.ONBOARDING.\n\nServer-authoritative replacement for the browser-localStorage onboarding\nstore, so the post-signup gate and one-time tooltips hold across devices."},"OnboardingStateUpdate":{"properties":{"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"skipped":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Skipped"},"seen_tooltips":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Seen Tooltips"},"completed_actions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Completed Actions"}},"type":"object","title":"OnboardingStateUpdate","description":"Partial update merged into the stored state.\n\nScalars overwrite when supplied; list entries are unioned into the stored\nlists, so concurrent updates (e.g. two tabs marking different tooltips)\ndon't drop each other's items."},"OpenAIEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI embedding model.","default":"text-embedding-3-small","examples":["text-embedding-3-small"]}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenAILLMService":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI chat model to use.","default":"gpt-4.1","examples":["gpt-4.1","gpt-4.1-mini","gpt-4.1-nano","gpt-5","gpt-5-mini","gpt-5-nano","gpt-3.5-turbo"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"Override only if using an OpenAI-compatible API (e.g. local LLM, proxy).","default":"https://api.openai.com/v1"}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenAIRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"openai_realtime","title":"Provider","default":"openai_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI realtime (speech-to-speech) model.","default":"gpt-realtime-2","examples":["gpt-realtime-2"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice the model speaks in.","default":"alloy","examples":["alloy","ash","ballad","coral","echo","sage","shimmer","verse"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"OpenAI Realtime"},"OpenAISTTConfiguration":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI transcription model.","default":"gpt-4o-transcribe","examples":["gpt-4o-transcribe"]},"base_url":{"type":"string","title":"Base Url","description":"Override only if using an OpenAI-compatible API (e.g. local STT, proxy).","default":"https://api.openai.com/v1"}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenAITTSService":{"properties":{"provider":{"type":"string","const":"openai","title":"Provider","default":"openai"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenAI TTS model.","default":"gpt-4o-mini-tts","examples":["gpt-4o-mini-tts"]},"voice":{"type":"string","title":"Voice","description":"OpenAI TTS voice name.","default":"alloy"},"base_url":{"type":"string","title":"Base Url","description":"Override only if using an OpenAI-compatible API (e.g. local TTS, proxy).","default":"https://api.openai.com/v1"}},"type":"object","required":["api_key"],"title":"OpenAI"},"OpenRouterEmbeddingsConfiguration":{"properties":{"provider":{"type":"string","const":"openrouter","title":"Provider","default":"openrouter"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenRouter-hosted embedding model slug.","default":"openai/text-embedding-3-small","examples":["openai/text-embedding-3-small"]},"base_url":{"type":"string","title":"Base Url","description":"Override only if proxying OpenRouter through your own gateway.","default":"https://openrouter.ai/api/v1"}},"type":"object","required":["api_key"],"title":"Open Router"},"OpenRouterLLMConfiguration":{"properties":{"provider":{"type":"string","const":"openrouter","title":"Provider","default":"openrouter"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"OpenRouter model slug in 'vendor/model' form.","default":"openai/gpt-4.1","examples":["openai/gpt-4.1","openai/gpt-4.1-mini","anthropic/claude-sonnet-4","google/gemini-2.5-flash","google/gemini-2.0-flash","meta-llama/llama-3.3-70b-instruct","deepseek/deepseek-chat-v3-0324"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"Override only if proxying OpenRouter through your own gateway.","default":"https://openrouter.ai/api/v1"}},"type":"object","required":["api_key"],"title":"Open Router"},"OrganizationAIModelConfigurationResponse":{"properties":{"configuration":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Configuration"},"effective_configuration":{"additionalProperties":true,"type":"object","title":"Effective Configuration"},"source":{"type":"string","enum":["organization_v2","legacy_user_v1","empty"],"title":"Source"}},"type":"object","required":["configuration","effective_configuration","source"],"title":"OrganizationAIModelConfigurationResponse"},"OrganizationAIModelConfigurationV2":{"properties":{"version":{"type":"integer","const":2,"title":"Version","default":2},"mode":{"type":"string","enum":["dograh","byok"],"title":"Mode"},"dograh":{"anyOf":[{"$ref":"#/components/schemas/DograhManagedAIModelConfiguration"},{"type":"null"}]},"byok":{"anyOf":[{"$ref":"#/components/schemas/BYOKAIModelConfiguration"},{"type":"null"}]}},"type":"object","required":["mode"],"title":"OrganizationAIModelConfigurationV2"},"OrganizationContextResponse":{"properties":{"organization_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Organization Id"},"organization_provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Provider Id"},"model_services":{"$ref":"#/components/schemas/OrganizationModelServicesContext"}},"type":"object","required":["model_services"],"title":"OrganizationContextResponse"},"OrganizationModelServicesContext":{"properties":{"config_source":{"type":"string","enum":["organization_v2","legacy_user_v1","empty"],"title":"Config Source"},"has_model_configuration_v2":{"type":"boolean","title":"Has Model Configuration V2"},"managed_service_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Managed Service Version"},"uses_managed_service_v2":{"type":"boolean","title":"Uses Managed Service V2"}},"type":"object","required":["config_source","has_model_configuration_v2","uses_managed_service_v2"],"title":"OrganizationModelServicesContext"},"OrganizationPreferences":{"properties":{"test_phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Phone Number"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"}},"type":"object","title":"OrganizationPreferences"},"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 a key in the request body."},"type":{"type":"string","enum":["string","number","boolean","object","array"],"title":"Type","description":"JSON type for the resolved value.","llm_hint":"Allowed values are string, number, boolean, object, and array."},"value_template":{"type":"string","title":"Value Template","description":"Fixed value or template, e.g. {{initial_context.phone_number}}.","llm_hint":"Use {{initial_context.*}} for call-start context and {{gathered_context.*}} for values extracted during the call."},"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"},"RewindTextChatSessionRequest":{"properties":{"cursor_turn_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor Turn Id"},"expected_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Revision"}},"type":"object","title":"RewindTextChatSessionRequest"},"RimeTTSConfiguration":{"properties":{"provider":{"type":"string","const":"rime","title":"Provider","default":"rime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Rime TTS model.","default":"arcana","examples":["arcana","mistv3","mistv2","mist"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Rime voice ID.","default":"celeste"},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed multiplier.","default":1.0},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["en","de","fr","es","hi"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Rime"},"S3SignedUrlResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["url","expires_in"],"title":"S3SignedUrlResponse"},"SarvamLLMConfiguration":{"properties":{"provider":{"type":"string","const":"sarvam","title":"Provider","default":"sarvam"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Sarvam chat model. Use sarvam-30b for low-latency voice agents; sarvam-105b for complex multi-step reasoning.","default":"sarvam-30b","examples":["sarvam-30b","sarvam-105b"],"allow_custom_input":true},"temperature":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Temperature","description":"Sampling temperature. Sarvam recommends 0.5 for balanced conversational responses.","default":0.5}},"type":"object","required":["api_key"],"title":"Sarvam"},"SarvamSTTConfiguration":{"properties":{"provider":{"type":"string","const":"sarvam","title":"Provider","default":"sarvam"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Sarvam STT model. saarika:v2.5 transcribes in the spoken language; saaras:v3 is the recommended model with flexible output modes.","default":"saarika:v2.5","examples":["saarika:v2.5","saaras:v3"]},"language":{"type":"string","title":"Language","description":"BCP-47 language code. Use unknown for automatic language detection.","default":"unknown","examples":["unknown","hi-IN","bn-IN","gu-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","en-IN"],"model_options":{"saaras:v3":["unknown","hi-IN","bn-IN","gu-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","en-IN","as-IN","ur-IN","ne-IN","kok-IN","ks-IN","sd-IN","sa-IN","sat-IN","mni-IN","brx-IN","mai-IN","doi-IN"],"saarika:v2.5":["unknown","hi-IN","bn-IN","gu-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","en-IN"]}}},"type":"object","required":["api_key"],"title":"Sarvam"},"SarvamTTSConfiguration":{"properties":{"provider":{"type":"string","const":"sarvam","title":"Provider","default":"sarvam"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Sarvam TTS model (voice list depends on this).","default":"bulbul:v2","examples":["bulbul:v2","bulbul:v3"]},"voice":{"type":"string","title":"Voice","description":"Sarvam voice name or custom voice ID.","default":"anushka","examples":["anushka","manisha","vidya","arya","abhilash","karun","hitesh"],"allow_custom_input":true,"model_options":{"bulbul:v2":["anushka","manisha","vidya","arya","abhilash","karun","hitesh"],"bulbul:v3":["shubh","aditya","ritu","priya","neha","rahul","pooja","rohan","simran","kavya","amit","dev","ishita","shreya","ratan","varun","manan","sumit","roopa","kabir","aayan","ashutosh","advait","amelia","sophia","anand","tanya","tarun","sunny","mani","gokul","vijay","shruti","suhani","mohit","kavitha","rehan","soham","rupali"]}},"language":{"type":"string","title":"Language","description":"BCP-47 Indian-language code (e.g. hi-IN, en-IN).","default":"hi-IN","examples":["bn-IN","en-IN","gu-IN","hi-IN","kn-IN","ml-IN","mr-IN","od-IN","pa-IN","ta-IN","te-IN","as-IN"]},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed multiplier.","default":1.0}},"type":"object","required":["api_key"],"title":"Sarvam"},"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"},"SmallestAISTTConfiguration":{"properties":{"provider":{"type":"string","const":"smallest","title":"Provider","default":"smallest"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Smallest AI STT model. Supports 38 languages with real-time streaming.","default":"pulse","examples":["pulse"]},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code for transcription.","default":"en","examples":["en","hi","fr","de","es","it","nl","pl","ru","pt","bn","gu","kn","ml","mr","ta","te","pa","or","bg","cs","da","et","fi","hu","lt","lv","mt","ro","sk","sv","uk"],"allow_custom_input":true}},"type":"object","required":["api_key"],"title":"Smallest AI","description":"Smallest AI ultralow-latency TTS (Waves) and STT (Pulse) APIs.","provider_docs_url":"https://smallest.ai/docs"},"SmallestAITTSConfiguration":{"properties":{"provider":{"type":"string","const":"smallest","title":"Provider","default":"smallest"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Smallest AI TTS model. lightning_v3.1_pro is the premium pool (American, British, Indian accents); lightning_v3.1 is the standard pool with 217 voices across 12 languages.","default":"lightning_v3.1","examples":["lightning_v3.1","lightning_v3.1_pro"]},"voice":{"type":"string","title":"Voice","description":"Smallest AI voice ID. Available voices differ by model: lightning_v3.1 has a broad multilingual pool; lightning_v3.1_pro has premium American, British, and Indian accent voices (English + Hindi only).","default":"sophia","examples":["sophia","avery","liam","lucas","olivia","ryan","freya","william","devansh","arjun","niharika","maya","dhruv","mia","maithili"],"allow_custom_input":true,"model_options":{"lightning_v3.1":["sophia","avery","liam","lucas","olivia","ryan","freya","william","devansh","arjun","niharika","maya","dhruv","mia","maithili"],"lightning_v3.1_pro":["meher","rhea","aviraj","cressida","willow","maverick"]}},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code for synthesis.","default":"en","examples":["en","hi","fr","de","es","it","nl","pl","ru","ar","bn","gu","he","kn","mr","ta"],"allow_custom_input":true},"speed":{"type":"number","maximum":2.0,"minimum":0.5,"title":"Speed","description":"Speech speed multiplier (0.5 to 2.0).","default":1.0}},"type":"object","required":["api_key"],"title":"Smallest AI","description":"Smallest AI ultralow-latency TTS (Waves) and STT (Pulse) APIs.","provider_docs_url":"https://smallest.ai/docs"},"SpeachesLLMConfiguration":{"properties":{"provider":{"type":"string","const":"speaches","title":"Provider","default":"speaches"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Usually not required for self-hosted endpoints. Leave blank unless your server enforces one."},"model":{"type":"string","title":"Model","description":"Model name as exposed by your OpenAI-compatible server.","default":"llama3","examples":["llama3","mistral","phi3","qwen2","gemma2","deepseek-r1"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"OpenAI-compatible endpoint (Ollama, vLLM, etc.).","default":"http://localhost:11434/v1"}},"type":"object","title":"Local Models (Speaches)","description":"Self-hosted OpenAI-compatible local models. See the Speaches project for setup and supported backends.","provider_docs_url":"https://github.com/speaches-ai/speaches"},"SpeachesSTTConfiguration":{"properties":{"provider":{"type":"string","const":"speaches","title":"Provider","default":"speaches"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Usually not required for self-hosted STT. Leave blank unless enforced."},"model":{"type":"string","title":"Model","description":"Whisper model identifier as served by your STT endpoint.","default":"Systran/faster-distil-whisper-small.en","examples":["Systran/faster-distil-whisper-small.en","Systran/faster-whisper-large-v3"],"allow_custom_input":true},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["en","ar","nl","fr","de","hi","it","pt","es"],"allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"OpenAI-compatible STT endpoint (Speaches, etc.).","default":"http://localhost:8000/v1"}},"type":"object","title":"Local Models (Speaches)","description":"Self-hosted OpenAI-compatible local models. See the Speaches project for setup and supported backends.","provider_docs_url":"https://github.com/speaches-ai/speaches"},"SpeachesTTSConfiguration":{"properties":{"provider":{"type":"string","const":"speaches","title":"Provider","default":"speaches"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Api Key","description":"Usually not required for self-hosted TTS. Leave blank unless enforced."},"model":{"type":"string","title":"Model","description":"Model name as served by your TTS endpoint (e.g. Kokoro-FastAPI).","default":"kokoro","examples":["hexgrad/Kokoro-82M"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Voice ID for the TTS engine.","default":"af_heart","allow_custom_input":true},"base_url":{"type":"string","title":"Base Url","description":"OpenAI-compatible TTS endpoint (Kokoro-FastAPI, etc.).","default":"http://localhost:8000/v1"},"speed":{"type":"number","maximum":4.0,"minimum":0.25,"title":"Speed","description":"Speech speed (0.25 to 4.0).","default":1.0}},"type":"object","title":"Local Models (Speaches)","description":"Self-hosted OpenAI-compatible local models. See the Speaches project for setup and supported backends.","provider_docs_url":"https://github.com/speaches-ai/speaches"},"SpeechmaticsSTTConfiguration":{"properties":{"provider":{"type":"string","const":"speechmatics","title":"Provider","default":"speechmatics"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Speechmatics operating point: 'standard' or 'enhanced'.","default":"enhanced"},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code.","default":"en","examples":["ar","ar_en","ba","eu","be","bn","bg","yue","ca","hr","cs","da","nl","en","eo","et","fi","fr","gl","de","el","he","hi","hu","id","ia","ga","it","ja","ko","lv","lt","ms","en_ms","mt","cmn","cmn_en","cmn_en_ms_ta","mr","mn","no","fa","pl","pt","ro","ru","sk","sl","es","sw","sv","tl","ta","en_ta","th","tr","uk","ur","ug","vi","cy"]}},"type":"object","required":["api_key"],"title":"Speechmatics"},"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 a key in the tool request body.","llm_hint":"Use a stable snake_case name the agent can naturally fill."},"type":{"type":"string","enum":["string","number","boolean","object","array"],"title":"Type","description":"JSON type for the parameter value.","llm_hint":"Allowed values are string, number, boolean, object, and array."},"description":{"type":"string","title":"Description","description":"Description shown to the model for this parameter.","llm_hint":"Write this as an instruction to the agent: what value to provide and when."},"required":{"type":"boolean","title":"Required","description":"Whether this parameter is required when the tool is called.","default":true}},"type":"object","required":["name","type","description"],"title":"ToolParameter","description":"A parameter that the tool accepts from the model at call time."},"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 reusable tool."},"TransferCallConfig":{"properties":{"destination":{"type":"string","title":"Destination","description":"Phone number or SIP endpoint to transfer the call to, e.g. +1234567890 or 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."},"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 seconds to wait for the destination to answer.","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."},"UltravoxRealtimeLLMConfiguration":{"properties":{"provider":{"type":"string","const":"ultravox_realtime","title":"Provider","default":"ultravox_realtime"},"api_key":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Api Key"},"model":{"type":"string","title":"Model","description":"Ultravox realtime voice-agent model.","default":"ultravox-v0.7","examples":["ultravox-v0.7","fixie-ai/ultravox"],"allow_custom_input":true},"voice":{"type":"string","title":"Voice","description":"Ultravox voice name or voice ID.","default":"Mark"}},"type":"object","required":["api_key"],"title":"Ultravox Realtime"},"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."},"UpdateFolderRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"UpdateFolderRequest"},"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 reusable 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"},"folder_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Folder Id"},"workflow_uuid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Uuid"}},"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"},"user_recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Recording Url"},"bot_recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bot Recording Url"},"transcript_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Public Url"},"recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Public Url"},"user_recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Recording Public Url"},"bot_recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bot Recording Public Url"},"public_access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Access Token"},"cost_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cost Info"},"usage_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Usage 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"},"WorkflowRunTextSessionResponse":{"properties":{"workflow_run_id":{"type":"integer","title":"Workflow Run Id"},"workflow_id":{"type":"integer","title":"Workflow Id"},"name":{"type":"string","title":"Name"},"mode":{"type":"string","title":"Mode"},"state":{"type":"string","title":"State"},"is_completed":{"type":"boolean","title":"Is Completed"},"revision":{"type":"integer","title":"Revision"},"initial_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Initial Context"},"gathered_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Gathered Context"},"annotations":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annotations"},"session_data":{"additionalProperties":true,"type":"object","title":"Session Data"},"checkpoint":{"additionalProperties":true,"type":"object","title":"Checkpoint"},"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":["workflow_run_id","workflow_id","name","mode","state","is_completed","revision","session_data","checkpoint","created_at"],"title":"WorkflowRunTextSessionResponse"},"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"},"user_recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Recording Url"},"bot_recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bot Recording Url"},"recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Public Url"},"transcript_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Public Url"},"user_recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Recording Public Url"},"bot_recording_public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bot Recording Public Url"},"public_access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Access Token"},"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"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode"},"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/docs/configurations/api-keys.mdx b/docs/configurations/api-keys.mdx index 7471c7ba..924abf60 100644 --- a/docs/configurations/api-keys.mdx +++ b/docs/configurations/api-keys.mdx @@ -15,5 +15,12 @@ Please note that you must copy and keep the API key secretly, since this is the ### Service Keys Service Keys are the keys which you generate to be used in [Model Configurations](inference-providers). In order to generate that, you can go to `/api-keys` and create a new key. + +You can use a Service Key created in Dograh Cloud (`https://app.dograh.com/api-keys`) in your self-hosted Dograh deployment. Create the Service Key from your Dograh Cloud account, then paste it into **Model Configurations** in your self-hosted instance to use Dograh-managed inference providers. You can purchase Dograh credits in Dograh Cloud; billing happens on the Cloud account that owns the Service Key. + + ![Create a new Service Key](../images/service-keys.png) + +Service Keys are scoped to the Dograh Cloud account that created them. You cannot use a Service Key from one cloud-hosted account in another cloud-hosted account; create a new Service Key from the account where you want to use it. + diff --git a/docs/configurations/inference-providers.mdx b/docs/configurations/inference-providers.mdx index d31dbc81..9fb12d16 100644 --- a/docs/configurations/inference-providers.mdx +++ b/docs/configurations/inference-providers.mdx @@ -1,187 +1,93 @@ --- title: "Model Configurations" -description: "Voice Agents need AI Models to work, like LLM (Large Language Model), TTS (Voice) and STT (Transcriber). You can use any of your faviourite providers with Dograh Platform to run your Voice Agent." +description: "Configure the speech-to-speech, Dograh-managed, or bring-your-own-key models your Dograh agents use." --- -## How Model Configuration Works +## How model configurations work -Dograh uses a **two-level configuration system** for AI models: +Model Configurations define the default AI model setup for your organization. Agents use this configuration unless you set agent-level model overrides in the agent settings. -1. **Global configuration** — A single set of model settings (LLM, TTS, STT) that applies to **all agents** by default. -2. **Agent-level overrides** — Optional per-agent settings that override the global configuration for specific services. +To configure models, open **Models** in your Dograh dashboard: -If no overrides are set for an agent, it uses the global configuration as-is. - - -Agent-level overrides are **selective** — you can override only the services you want to change. For example, you can override just the LLM provider for a specific agent while keeping the global TTS and STT settings. There is no need to reconfigure every service. - - -## Global Configuration - -The global configuration is the default model setup shared across all your agents. Dograh ships with its own models by default — when you sign up on https://app.dograh.com or set up the platform on your self-hosted infrastructure, you get some Dograh model credits to start with. - -To configure the global models, go to **Model Configurations** in your dashboard: - **Hosted:** `https://app.dograh.com/model-configurations` - **Self-hosted:** `http://localhost:3010/model-configurations` +- **Local development:** `http://localhost:3000/model-configurations` -![Model Configuration](../images/service-configuration.png) +The Models page has three top-level sections: -From here you can configure each service: - -| Service | What it does | -|---------|-------------| -| **LLM** | The language model that generates responses (e.g., OpenAI GPT-4.1, Anthropic Claude) | -| **TTS (Voice)** | The text-to-speech model that converts responses to spoken audio (e.g., ElevenLabs, Cartesia) | -| **STT (Transcriber)** | The speech-to-text model that transcribes user speech (e.g., Deepgram, AssemblyAI) | -| **Realtime** | A single speech-to-speech model that handles LLM, TTS, and STT in one (e.g., Gemini Live) | - -Select a provider from the dropdown and configure the API key, model, and any provider-specific settings. For Dograh's own models, see [Service Keys](api-keys) for instructions on creating Service Keys. - -## Agent-Level Model Overrides - -You can override the global model configuration for any individual agent. This is useful when different agents have different requirements — for example, a customer support agent might use a faster, cheaper LLM while a sales agent uses a more capable one. - -### Configuring overrides - -1. Open the agent you want to customize. -2. Go to **Settings** in the agent detail page. -3. Select the **Model Overrides** tab. -4. You will see tabs for each service: **LLM**, **Voice** (TTS), and **Transcriber** (STT). -5. Toggle **Override** on for the service you want to change. -6. Configure the provider, model, and other settings as needed. -7. Save your changes. - -### Selective overrides - -Each service can be toggled independently. When an override is **off** for a service, the agent inherits the global setting for that service. When an override is **on**, the agent uses the override setting instead. - -| LLM Override | TTS Override | STT Override | Result | -|---|---|---|---| -| Off | Off | Off | Agent uses global config for all services | -| On | Off | Off | Agent uses custom LLM, global TTS and STT | -| Off | On | Off | Agent uses global LLM and STT, custom TTS | -| On | On | On | Agent uses custom config for all services | - -For example, if you only want to change the voice for a specific agent: -1. Leave the LLM and Transcriber overrides **off**. -2. Toggle the Voice override **on**. -3. Select a different TTS provider or voice. -4. The agent will use your custom voice while still using the global LLM and STT. - -### Realtime mode override - -You can also switch an individual agent to use a **Realtime** provider (such as Gemini Live) even if the global configuration uses standard LLM + TTS + STT. Toggle the **Realtime** switch in the Model Overrides tab, then configure the realtime provider, model, and voice. +| Section | When to use it | +|---------|----------------| +| **Speech to Speech** | Use a realtime speech-to-speech model for the live conversation. You still configure an LLM alongside it for variable extraction and QA. | +| **Dograh** | Use Dograh-managed LLM, voice, and transcriber models behind one Dograh Service Key. | +| **BYOK** | Bring your own provider keys and configure LLM, Voice, Transcriber, and Embedding models separately. | -When an agent uses a Realtime provider, it replaces the separate TTS and STT services with a single speech-to-speech model. An **LLM** is still required alongside the Realtime model — it's used for out-of-band tasks like variable extraction and QA analysis, which the realtime service does not handle. Context compaction is not applicable in Realtime mode and is ignored if enabled. +Model settings are organization-scoped. If no agent-level override is set, every agent in the organization uses the saved global configuration. -## Gemini 3.1 Live +## Speech to Speech -Gemini 3.1 Live is Google's realtime multimodal API that handles both LLM and voice in a single model. Instead of configuring separate LLM, TTS, and STT services, Gemini Live acts as an all-in-one realtime provider — it processes speech input, generates a response, and speaks it back, all over a single streaming connection. +Use **Speech to Speech** when you want a realtime model to handle the live spoken conversation directly. In this mode, the realtime model handles speech input and speech output, so you do not configure separate Voice and Transcriber services. -Dograh supports Gemini 3.1 Live as a **Realtime** provider. The default model is `gemini-3.1-flash-live-preview`. +![Speech to Speech model configuration](../images/model-configuration-speech-to-speech.png) -### Available Voices +The Speech to Speech section has nested tabs: -You can choose from the following built-in voices: +| Tab | What to configure | +|-----|-------------------| +| **Realtime Model** | The speech-to-speech provider, model, voice, and API key. | +| **LLM** | A standard LLM used for non-realtime tasks such as variable extraction and QA analysis. | +| **Embedding** | An embedding model used by features that need embeddings, such as retrieval from knowledge base content. | -| Voice | Description | -|-------|-------------| -| Puck | Default voice | -| Charon | — | -| Kore | — | -| Fenrir | — | -| Aoede | — | + +An LLM is still required when you use Speech to Speech. The realtime model handles the live voice conversation, but Dograh uses the LLM for analysis tasks that happen outside the live audio stream. + -### Getting a Gemini API Key +## Dograh -To use Gemini 3.1 Live with Dograh, you need a Google Gemini API key. Follow these steps: +Use **Dograh** when you want Dograh to manage the model providers for you. This path uses one Dograh Service Key for Dograh-managed models instead of separate provider keys for LLM, Voice, and Transcriber. -1. Go to [Google AI Studio](https://aistudio.google.com/). -2. Sign in with your Google account. -3. Click on **Get API Key** in the left sidebar. -4. Click **Create API Key**. -5. Select an existing Google Cloud project or create a new one. -6. Copy the generated API key and store it securely. +![Dograh model configuration](../images/model-configuration-dograh.png) - - The Gemini API key is different from a Google Cloud service account key. You specifically need a **Gemini API key** from Google AI Studio for use with Dograh. - +Configure: -### Configuring Gemini 3.1 Live in Dograh +| Field | What it controls | +|-------|------------------| +| **Voice** | The Dograh-managed voice to use. | +| **Speed** | The voice playback speed. | +| **Language** | The language behavior, including multilingual auto-detect when available. | +| **API Key** | Your Dograh Service Key. Create Service Keys from **Developers**. | -1. Go to **Model Configurations** in your Dograh dashboard (`https://app.dograh.com/model-configurations` for hosted or `http://localhost:3010/model-configurations` for local). -2. Under the **Realtime** section, select `google_realtime` as the provider. -3. Paste your Gemini API key. -4. Select the model (`gemini-3.1-flash-live-preview` is available by default, or you can enter a model name manually). -5. Choose a voice from the dropdown (default is `Puck`). -6. Select the language (currently `en` is supported). +For details on creating and using Service Keys, see [API Keys and Service Keys](api-keys#service-keys). - - When using a Realtime provider like Gemini Live, you do not need to configure separate TTS and STT services — the realtime model handles speech in and out. However, you **must** still configure an **LLM** under the LLM tab: it powers variable extraction and QA analysis, which the realtime service does not perform. - +## BYOK -## Gemini Live on Vertex AI +Use **BYOK** when you want to bring your own provider accounts and API keys. This gives you separate control over each model category. -If you want to run Gemini Live through your own Google Cloud project — for billing consolidation, VPC controls, regional residency, or enterprise IAM — Dograh also supports Gemini Live via **Vertex AI** as a separate provider (`google_vertex_realtime`). The default model is `google/gemini-live-2.5-flash-native-audio`. +![BYOK model configuration](../images/model-configuration-byok.png) -Unlike Google AI Studio (which uses a single Gemini API key), Vertex AI authenticates with a **service account** belonging to your Google Cloud project. +The BYOK section has nested tabs: -### Prerequisites +| Tab | What to configure | +|-----|-------------------| +| **LLM** | The chat or reasoning model provider, model, optional base URL, and API key. | +| **Voice** | The text-to-speech provider, voice, model, speed, optional base URL, and API key. | +| **Transcriber** | The speech-to-text provider, model, language, and API key. | +| **Embedding** | The embedding provider, model, and API key. | -1. A Google Cloud project with billing enabled. -2. The Vertex AI API enabled on that project: +Provider-specific fields appear only when they apply. For example, OpenAI-compatible LLM providers can expose a **Base URL** field, ElevenLabs voices can expose a voice ID, and transcribers can expose language options. - ```bash - gcloud services enable aiplatform.googleapis.com --project=YOUR_PROJECT_ID - ``` +## Agent-level model overrides -3. A service account with the **Vertex AI User** role (`roles/aiplatform.user`) on the project: +You can override the organization model configuration for an individual agent. This is useful when different agents need different models, voices, transcribers, or providers. - ```bash - gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ - --member="serviceAccount:YOUR_SA@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ - --role="roles/aiplatform.user" - ``` +To configure an override: -4. A **JSON** key for that service account (P12 keys are not supported). +1. Open the agent. +2. Go to **Settings**. +3. Open **Model Overrides**. +4. Enable the override for the service you want to customize. +5. Configure the provider, model, and keys for that service. +6. Save the agent settings. -### Creating the service account key - -1. In the GCP Console, go to **IAM & Admin → Service Accounts**. -2. Pick an existing service account (or create a new one). -3. Open the **Keys** tab → **Add Key → Create new key**. -4. Choose **JSON** as the key type and click **Create**. -5. The key file will download to your computer — store it securely and treat it as a secret. - - - Always pick **JSON**, not P12. The Vertex AI client libraries used by Dograh only accept service-account JSON keys; P12 is a legacy format retained for older Google Workspace integrations. - - -### Configuring Vertex AI Realtime in Dograh - -1. Go to **Model Configurations** in your Dograh dashboard. -2. Enable the **Realtime** toggle. -3. Under the **Realtime** section, select `google_vertex_realtime` as the provider. -4. Fill in the fields: - - | Field | What to put in | - |---|---| - | **Model** | Vertex publisher/model id, e.g. `google/gemini-live-2.5-flash-native-audio` | - | **Voice** | One of the built-in voices (Puck, Charon, Kore, Fenrir, Aoede) | - | **Language** | BCP-47 code (e.g. `en-US`) | - | **Project Id** | The `project_id` value from your service-account JSON | - | **Location** | GCP region where the model is available (e.g. `us-east4`) | - | **Credentials** | Paste the **entire contents** of the service-account JSON file | - | **API Key** | Leave blank — Vertex AI does not use API keys | - -5. Save the configuration. - - - Paste the whole JSON file into the **Credentials** field — including `private_key`, `client_email`, and all other entries. Don't try to extract individual fields. If `Credentials` is left blank, Dograh falls back to **Application Default Credentials (ADC)** from the host environment, which is useful when running Dograh on a GCP VM or GKE pod with an attached service account. - - - - IAM changes can take up to ~60 seconds to propagate. If you see `Permission 'aiplatform.endpoints.predict' denied`, wait a minute and retry — or double-check that the role was granted to the same service account whose JSON you pasted. - \ No newline at end of file +Agent-level overrides are selective. For example, you can override only the Voice service for one agent while it continues to use the organization-level LLM and Transcriber configuration. diff --git a/docs/contribution/setup.mdx b/docs/contribution/setup.mdx index a2ed2495..beb7f8de 100644 --- a/docs/contribution/setup.mdx +++ b/docs/contribution/setup.mdx @@ -6,6 +6,23 @@ description: Set up the Dograh contributor environment with the devcontainer-fir If the steps below do not work for you, please open an issue on [GitHub](https://github.com/dograh-hq/dograh/issues). + +**Using Claude Code or Codex?** Install the official Dograh setup skill and let your agent walk you through the contributor setup — it covers both the devcontainer and host-managed paths, runs Dograh's own scripts, and verifies the stack is healthy. + + +```text Claude Code +/plugin marketplace add dograh-hq/dograh-plugins +/plugin install dograh@dograh +``` +```text Codex +codex plugin marketplace add dograh-hq/dograh-plugins +codex plugin add dograh@dograh +``` + + +Start a new session, then ask it to *"set up Dograh for development"* (or run `/dograh-setup develop` in Claude Code). More at [dograh-hq/dograh-plugins](https://github.com/dograh-hq/dograh-plugins). + + ### Recommended: Devcontainer Setup #### System Requirements diff --git a/docs/deployment/authentication.mdx b/docs/deployment/authentication.mdx new file mode 100644 index 00000000..47d05ed0 --- /dev/null +++ b/docs/deployment/authentication.mdx @@ -0,0 +1,99 @@ +--- +title: "Authentication" +description: "Configure how self-hosted Dograh authenticates users — the default local provider, or Stack Auth for social login" +--- + +Self-hosted Dograh ships with a built-in **local** authentication provider (email + password, backed by a signed JWT). This is the default and needs no external service. + +To offer social logins (Google, GitHub, and others), you can delegate sign-in to **[Stack Auth](https://stack-auth.com)**. Enabling it is a **runtime** configuration change — set a few environment variables and restart. The prebuilt `dograhai/dograh-api` and `dograhai/dograh-ui` images work as-is; you do **not** need to rebuild or build from source. + + +The active provider is controlled by the backend `AUTH_PROVIDER` variable (`local` by default). The frontend discovers the provider — and, for Stack, its public client config — at runtime from the backend's `/api/v1/health` response, so the browser bundle never needs Stack values baked in at build time. + + +## How it works + +1. The backend reads `AUTH_PROVIDER` and the Stack settings from its environment. +2. When `AUTH_PROVIDER=stack`, `/api/v1/health` returns the **public** Stack client config (project id + publishable client key). +3. The UI fetches that at runtime and initializes the Stack SDK in the browser. +4. The **secret server key** is used only server-side (by the backend and the UI's server runtime) and is never sent to the browser. + +## Prerequisites + +A Stack Auth project. Create one in the [Stack Auth dashboard](https://app.stack-auth.com) and configure the social login providers you want to offer. + +## Step 1 — Collect your Stack credentials + +From your project in the [Stack Auth dashboard](https://app.stack-auth.com), gather: + +| Value | Sensitivity | +|---|---| +| **Project ID** | Public | +| **Publishable client key** | Public (safe to expose in the browser) | +| **Secret server key** | Secret — keep server-side only | +| **API base URL** | Public. For Stack's hosted service this is `https://api.stack-auth.com` | + +## Step 2 — Configure the backend (`api`) + +Set these on the `api` service. Add them to the `environment:` block of the `api` service in your `docker-compose.yaml`: + +```yaml docker-compose.yaml +services: + api: + environment: + AUTH_PROVIDER: "stack" + STACK_AUTH_PROJECT_ID: "" + STACK_PUBLISHABLE_CLIENT_KEY: "" + STACK_SECRET_SERVER_KEY: "" + STACK_AUTH_API_URL: "https://api.stack-auth.com" +``` + +## Step 3 — Configure the UI (`ui`) + +The UI runs server-side code (SSR pages and the `/handler/*` auth routes) that calls Stack with the secret server key, so the `ui` service needs that one value too: + +```yaml docker-compose.yaml +services: + ui: + environment: + STACK_SECRET_SERVER_KEY: "" +``` + + +The `ui` service does **not** need the project id or publishable client key — it receives those from the backend at runtime via `/api/v1/health`. Only the secret server key (used server-side) is set here. + + +## Step 4 — Restart and verify + +Recreate the containers so they pick up the new environment: + +```bash +docker compose up -d +``` + +Confirm the backend reports the active provider and the public client config: + +```bash +curl -s http://localhost:8000/api/v1/health +# expect: "auth_provider":"stack", plus "stack_project_id" and "stack_publishable_client_key" +``` + +Then open the UI. The sign-in page should now present your configured Stack Auth social login options instead of the local email/password form. + +## Environment variable reference + +| Variable | Service | Secret | Notes | +|---|---|---|---| +| `AUTH_PROVIDER` | `api` | — | Set to `stack` (default `local`) | +| `STACK_AUTH_PROJECT_ID` | `api` | No | Stack project ID; served to the UI at runtime | +| `STACK_PUBLISHABLE_CLIENT_KEY` | `api` | No | Publishable key; served to the UI at runtime | +| `STACK_SECRET_SERVER_KEY` | `api` + `ui` | **Yes** | Server-side only — never exposed to the browser | +| `STACK_AUTH_API_URL` | `api` | No | Stack REST API base URL | + + +`STACK_SECRET_SERVER_KEY` is the only secret here. Keep it out of any client-visible config and never bake it into an image. The project ID and publishable client key are public by design — the backend deliberately serves them to the browser so Stack can initialize at runtime. + + +## Reverting to local auth + +Remove the variables above (or set `AUTH_PROVIDER=local`) and restart. The UI detects `local` from the backend at runtime and falls back to the built-in email/password flow — no rebuild required. diff --git a/docs/deployment/docker.mdx b/docs/deployment/docker.mdx index 28a6a8ba..04934e50 100644 --- a/docs/deployment/docker.mdx +++ b/docs/deployment/docker.mdx @@ -8,6 +8,23 @@ Dograh AI can be deployed using Docker in two main configurations. Choose the op - **Option 1**: For local development and testing on your own machine - **Option 2**: For remote server deployment with HTTPS (using IP address). If you also have a custom domain, you can first deploy Dograh stack on your server using steps in this document and then proceed to the [Custom Domain](deployment/custom-domain) section. + +**Using Claude Code or Codex?** Install the official Dograh setup skill and let your agent drive either deployment below — it orients to your OS, picks local vs remote, runs Dograh's own setup scripts, and verifies the result with a built-in health check. + + +```text Claude Code +/plugin marketplace add dograh-hq/dograh-plugins +/plugin install dograh@dograh +``` +```text Codex +codex plugin marketplace add dograh-hq/dograh-plugins +codex plugin add dograh@dograh +``` + + +Start a new session, then ask it to *"set up Dograh"* (or run `/dograh-setup` in Claude Code). More at [dograh-hq/dograh-plugins](https://github.com/dograh-hq/dograh-plugins). + + ## Option 1: Local Docker Deployment Watch the video tutorial below for a step-by-step walkthrough of setting up Dograh AI locally with Docker. diff --git a/docs/developer/environment-variables.mdx b/docs/developer/environment-variables.mdx index d7f480bf..559bc30b 100644 --- a/docs/developer/environment-variables.mdx +++ b/docs/developer/environment-variables.mdx @@ -22,7 +22,7 @@ The relevant required variables for each mode are noted in the descriptions belo |---|---|---| | `ENVIRONMENT` | `local` | Runtime environment. Affects logging and behaviour. One of `local`, `production`, `test` | | `DEPLOYMENT_MODE` | `oss` | Deployment mode. Use `oss` for self-hosted | -| `AUTH_PROVIDER` | `local` | Authentication provider. Use `local` for OSS | +| `AUTH_PROVIDER` | `local` | Authentication provider. `local` (default) uses the built-in email/password flow. Set to `stack` to delegate to Stack Auth for social login — see [Authentication](/deployment/authentication) for the full setup | --- @@ -48,6 +48,19 @@ Never use the placeholder `OSS_JWT_SECRET` in a production deployment. Generate --- +## Authentication (Stack Auth) + +Set these when `AUTH_PROVIDER=stack` to delegate sign-in to [Stack Auth](https://stack-auth.com) for social login. The project id and publishable client key are public and are served to the browser at runtime via `/api/v1/health`; the secret server key stays server-side. See [Authentication](/deployment/authentication) for the full walkthrough. + +| Variable | Default | Description | +|---|---|---| +| `STACK_AUTH_PROJECT_ID` | `null` | **Required for `stack`.** Stack project ID (public) | +| `STACK_PUBLISHABLE_CLIENT_KEY` | `null` | **Required for `stack`.** Stack publishable client key (public) | +| `STACK_SECRET_SERVER_KEY` | `null` | **Required for `stack`.** Stack secret server key — server-side only, also set on the `ui` service. Keep secret | +| `STACK_AUTH_API_URL` | `null` | **Required for `stack`.** Stack REST API base URL (e.g. `https://api.stack-auth.com`) | + +--- + ## URLs | Variable | Default | Description | diff --git a/docs/docs.json b/docs/docs.json index 2bb9924e..21f2225f 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -157,6 +157,7 @@ "pages": [ "deployment/introduction", "deployment/docker", + "deployment/authentication", "deployment/custom-domain", "deployment/scaling", "deployment/update", diff --git a/docs/getting-started/index.mdx b/docs/getting-started/index.mdx index 09252089..cba5988c 100644 --- a/docs/getting-started/index.mdx +++ b/docs/getting-started/index.mdx @@ -39,6 +39,23 @@ Invoke-WebRequest -OutFile start_docker.ps1 https://raw.githubusercontent.com/do ``` + +**Using an AI coding agent?** If you work in **Claude Code** or **Codex**, install the official Dograh setup skill and let your agent handle installation, configuration, and troubleshooting for you. It orients to your OS, picks the right deploy path, runs Dograh's own setup scripts, and verifies the result. Install it once: + + +```text Claude Code +/plugin marketplace add dograh-hq/dograh-plugins +/plugin install dograh@dograh +``` +```text Codex +codex plugin marketplace add dograh-hq/dograh-plugins +codex plugin add dograh@dograh +``` + + +Then start a new session and ask it to *"set up Dograh"* (in Claude Code you can also run `/dograh-setup`). See [dograh-hq/dograh-plugins](https://github.com/dograh-hq/dograh-plugins) for details. + + Please check [Prerequisites](getting-started/prerequisites) for the system requirements and [Troubleshooting](getting-started/troubleshooting) for common issues. ## Next Steps diff --git a/docs/images/model-configuration-byok.png b/docs/images/model-configuration-byok.png new file mode 100644 index 00000000..0413d718 Binary files /dev/null and b/docs/images/model-configuration-byok.png differ diff --git a/docs/images/model-configuration-dograh.png b/docs/images/model-configuration-dograh.png new file mode 100644 index 00000000..7f4d0a9b Binary files /dev/null and b/docs/images/model-configuration-dograh.png differ diff --git a/docs/images/model-configuration-speech-to-speech.png b/docs/images/model-configuration-speech-to-speech.png new file mode 100644 index 00000000..52b5d99b Binary files /dev/null and b/docs/images/model-configuration-speech-to-speech.png differ diff --git a/docs/images/service-configuration.png b/docs/images/service-configuration.png deleted file mode 100644 index 2cbc3506..00000000 Binary files a/docs/images/service-configuration.png and /dev/null differ diff --git a/examples/README.md b/examples/README.md index 802ed730..22e5c918 100644 --- a/examples/README.md +++ b/examples/README.md @@ -26,6 +26,14 @@ python python/fetch_workflow_and_call.py # Create a new workflow from a definition. python python/create_workflow.py + +# Build a 3-node agent with the Workflow SDK and save it as a draft. +# Edit WORKFLOW_ID at the top of the file first. +python python/build_workflow_with_sdk.py + +# Load an existing workflow, edit the startCall prompt, and save as a draft. +# Edit WORKFLOW_ID at the top of the file first. +python python/load_and_edit_workflow.py ``` ## TypeScript @@ -41,4 +49,6 @@ export DOGRAH_API_TOKEN=sk-... npm run call # fetch_workflow_and_call.ts npm run create # create_workflow.ts +npm run build # build_workflow_with_sdk.ts (edit WORKFLOW_ID in the file first) +npm run edit # load_and_edit_workflow.ts (edit WORKFLOW_ID in the file first) ``` diff --git a/examples/python/build_workflow_with_sdk.py b/examples/python/build_workflow_with_sdk.py new file mode 100644 index 00000000..78d47955 --- /dev/null +++ b/examples/python/build_workflow_with_sdk.py @@ -0,0 +1,101 @@ +"""Build a multi-node voice agent using the Workflow SDK and save it as a draft. + +Requirements: + pip install -r requirements.txt + +Environment variables (loaded from `.env` in this directory): + DOGRAH_API_ENDPOINT - Dograh API base URL (e.g. http://localhost:8000) + DOGRAH_API_TOKEN - API token sent as X-API-Key + +Run: + python build_workflow_with_sdk.py +""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +from dotenv import load_dotenv + +from dograh_sdk import DograhClient, Workflow + +load_dotenv(Path(__file__).parent / ".env") + +# Replace with the numeric ID of an existing agent in your Dograh account. +# Create one via the UI or with create_workflow.py if you don't have one yet. +WORKFLOW_ID = 0 + + +def main() -> int: + api_endpoint = os.environ.get("DOGRAH_API_ENDPOINT", "http://localhost:8000") + api_token = os.environ.get("DOGRAH_API_TOKEN") + + if not api_token: + print("DOGRAH_API_TOKEN is required", file=sys.stderr) + return 1 + + if WORKFLOW_ID == 0: + print("Set WORKFLOW_ID at the top of this file to an existing workflow ID", file=sys.stderr) + return 1 + + with DograhClient(base_url=api_endpoint, api_key=api_token) as client: + existing = client.get_workflow(WORKFLOW_ID) + # Preserve the live workflow name; save_workflow sends name with the draft update. + wf = Workflow(client=client, name=existing.name) + + greeting = wf.add( + type="startCall", + name="greeting", + prompt=( + "# Goal\n" + "You are a helpful agent having a conversation over voice with a human. " + "This is a voice conversation, so transcripts can be error prone.\n\n" + "## Flow\n" + "Greet the caller warmly and ask whether they would like to continue." + ), + ) + qualify = wf.add( + type="agentNode", + name="qualify", + prompt=( + "# Goal\n" + "Qualify the lead by asking about their needs, budget, and timeline.\n\n" + "## Rules\n" + "- Keep responses short — 2-3 sentences max\n" + "- Confirm all three answers before moving on" + ), + ) + done = wf.add( + type="endCall", + name="done", + prompt="Thank the caller for their time and let them know the team will follow up shortly.", + ) + + wf.edge( + greeting, + qualify, + label="interested", + condition="Caller confirms they want to continue.", + ) + wf.edge( + qualify, + done, + label="qualified", + condition="All qualification questions have been answered.", + ) + + result = client.save_workflow(workflow_id=WORKFLOW_ID, workflow=wf) + node_count = len(result.workflow_definition.get("nodes", [])) + print( + f"Saved workflow {result.id}: {result.name!r} " + f"(version={result.version_number}, status={result.version_status}, " + f"nodes={node_count})" + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/python/load_and_edit_workflow.py b/examples/python/load_and_edit_workflow.py new file mode 100644 index 00000000..e5f92157 --- /dev/null +++ b/examples/python/load_and_edit_workflow.py @@ -0,0 +1,75 @@ +"""Load an existing workflow, edit a node prompt, and save it as a draft. + +Requirements: + pip install -r requirements.txt + +Environment variables (loaded from `.env` in this directory): + DOGRAH_API_ENDPOINT - Dograh API base URL (e.g. http://localhost:8000) + DOGRAH_API_TOKEN - API token sent as X-API-Key + +Run: + python load_and_edit_workflow.py +""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +from dotenv import load_dotenv + +from dograh_sdk import DograhClient +from dograh_sdk._generated_models import UpdateWorkflowRequest + +load_dotenv(Path(__file__).parent / ".env") + +# Replace with the numeric ID of an existing agent in your Dograh account. +WORKFLOW_ID = 0 + +# Sentence appended to the startCall node's prompt when the script runs. +PROMPT_SUFFIX = " Please be concise — keep all responses under two sentences." + + +def main() -> int: + api_endpoint = os.environ.get("DOGRAH_API_ENDPOINT", "http://localhost:8000") + api_token = os.environ.get("DOGRAH_API_TOKEN") + + if not api_token: + print("DOGRAH_API_TOKEN is required", file=sys.stderr) + return 1 + + if WORKFLOW_ID == 0: + print("Set WORKFLOW_ID at the top of this file to an existing workflow ID", file=sys.stderr) + return 1 + + with DograhClient(base_url=api_endpoint, api_key=api_token) as client: + existing = client.get_workflow(WORKFLOW_ID) + print(f"Loaded workflow {existing.id}: {existing.name!r} (status={existing.status})") + + definition = dict(existing.workflow_definition) + + for node in definition.get("nodes", []): + if node.get("type") == "startCall": + data = dict(node.get("data") or {}) + data["prompt"] = (data.get("prompt") or "") + PROMPT_SUFFIX + node["data"] = data + break + + result = client.update_workflow( + WORKFLOW_ID, + body=UpdateWorkflowRequest( + name=existing.name, + workflow_definition=definition, + ), + ) + print( + f"Saved draft for workflow {result.id}: {result.name!r} " + f"(version={result.version_number}, status={result.version_status})" + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/typescript/build_workflow_with_sdk.ts b/examples/typescript/build_workflow_with_sdk.ts new file mode 100644 index 00000000..4dfb77a7 --- /dev/null +++ b/examples/typescript/build_workflow_with_sdk.ts @@ -0,0 +1,84 @@ +// Build a multi-node voice agent using the Workflow SDK and save it as a draft. +// +// Requirements: +// npm install @dograh/sdk +// +// Environment variables: +// DOGRAH_API_ENDPOINT - Dograh API base URL (e.g. http://localhost:8000) +// DOGRAH_API_TOKEN - API token sent as X-API-Key +// +// Run: +// npx tsx build_workflow_with_sdk.ts + +import { DograhClient, Workflow } from "@dograh/sdk"; + +// Replace with the numeric ID of an existing agent in your Dograh account. +// Create one via the UI or with create_workflow.ts if you don't have one yet. +const WORKFLOW_ID = 0; + +async function main(): Promise { + const apiEndpoint = process.env.DOGRAH_API_ENDPOINT ?? "http://localhost:8000"; + const apiToken = process.env.DOGRAH_API_TOKEN; + + if (!apiToken) throw new Error("DOGRAH_API_TOKEN is required"); + if (WORKFLOW_ID === 0) throw new Error("Set WORKFLOW_ID at the top of this file to an existing workflow ID"); + + const client = new DograhClient({ + baseUrl: apiEndpoint, + apiKey: apiToken, + }); + + const existing = await client.getWorkflow(WORKFLOW_ID); + // Preserve the live workflow name; saveWorkflow sends name with the draft update. + const wf = new Workflow({ client, name: existing.name }); + + const greeting = await wf.add({ + type: "startCall", + name: "greeting", + prompt: [ + "# Goal", + "You are a helpful agent having a conversation over voice with a human. This is a voice conversation, so transcripts can be error prone.", + "", + "## Flow", + "Greet the caller warmly and ask whether they would like to continue.", + ].join("\n"), + }); + const qualify = await wf.add({ + type: "agentNode", + name: "qualify", + prompt: [ + "# Goal", + "Qualify the lead by asking about their needs, budget, and timeline.", + "", + "## Rules", + "- Keep responses short — 2-3 sentences max", + "- Confirm all three answers before moving on", + ].join("\n"), + }); + const done = await wf.add({ + type: "endCall", + name: "done", + prompt: "Thank the caller for their time and let them know the team will follow up shortly.", + }); + + wf.edge(greeting, qualify, { + label: "interested", + condition: "Caller confirms they want to continue.", + }); + wf.edge(qualify, done, { + label: "qualified", + condition: "All qualification questions have been answered.", + }); + + const result = await client.saveWorkflow(WORKFLOW_ID, wf); + const nodeCount = ((result.workflow_definition?.nodes as unknown[]) ?? []).length; + console.log( + `Saved workflow ${result.id}: ${JSON.stringify(result.name)} ` + + `(version=${result.version_number}, status=${result.version_status}, nodes=${nodeCount})`, + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/examples/typescript/load_and_edit_workflow.ts b/examples/typescript/load_and_edit_workflow.ts new file mode 100644 index 00000000..4c620bca --- /dev/null +++ b/examples/typescript/load_and_edit_workflow.ts @@ -0,0 +1,63 @@ +// Load an existing workflow, edit a node prompt, and save it as a draft. +// +// Requirements: +// npm install @dograh/sdk +// +// Environment variables: +// DOGRAH_API_ENDPOINT - Dograh API base URL (e.g. http://localhost:8000) +// DOGRAH_API_TOKEN - API token sent as X-API-Key +// +// Run: +// npx tsx load_and_edit_workflow.ts + +import { DograhClient } from "@dograh/sdk"; + +// Replace with the numeric ID of an existing agent in your Dograh account. +const WORKFLOW_ID = 0; + +// Sentence appended to the startCall node's prompt when the script runs. +const PROMPT_SUFFIX = " Please be concise — keep all responses under two sentences."; + +async function main(): Promise { + const apiEndpoint = process.env.DOGRAH_API_ENDPOINT ?? "http://localhost:8000"; + const apiToken = process.env.DOGRAH_API_TOKEN; + + if (!apiToken) throw new Error("DOGRAH_API_TOKEN is required"); + if (WORKFLOW_ID === 0) throw new Error("Set WORKFLOW_ID at the top of this file to an existing workflow ID"); + + const client = new DograhClient({ + baseUrl: apiEndpoint, + apiKey: apiToken, + }); + + const existing = await client.getWorkflow(WORKFLOW_ID); + console.log(`Loaded workflow ${existing.id}: ${JSON.stringify(existing.name)} (status=${existing.status})`); + + const definition = structuredClone(existing.workflow_definition) as { + nodes?: Array<{ type?: string; data?: Record }>; + }; + + for (const node of definition.nodes ?? []) { + if (node.type === "startCall") { + node.data = node.data ?? {}; + node.data.prompt = ((node.data.prompt as string) ?? "") + PROMPT_SUFFIX; + break; + } + } + + const result = await client.updateWorkflow(WORKFLOW_ID, { + body: { + name: existing.name, + workflow_definition: definition as Record, + }, + }); + console.log( + `Saved draft for workflow ${result.id}: ${JSON.stringify(result.name)} ` + + `(version=${result.version_number}, status=${result.version_status})`, + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/examples/typescript/package.json b/examples/typescript/package.json index c48f2281..68e3782e 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -5,7 +5,9 @@ "type": "module", "scripts": { "call": "tsx --env-file=.env fetch_workflow_and_call.ts", - "create": "tsx --env-file=.env create_workflow.ts" + "create": "tsx --env-file=.env create_workflow.ts", + "build": "tsx --env-file=.env build_workflow_with_sdk.ts", + "edit": "tsx --env-file=.env load_and_edit_workflow.ts" }, "dependencies": { "@dograh/sdk": "latest" diff --git a/pipecat b/pipecat index 0d64dc6e..85a48a37 160000 --- a/pipecat +++ b/pipecat @@ -1 +1 @@ -Subproject commit 0d64dc6e0e3e6b3c46cc66373e34b4f54f980268 +Subproject commit 85a48a37bf51e5b8854a85a2ff6319c67937b6fa diff --git a/scripts/setup_pipecat.sh b/scripts/setup_pipecat.sh index 04821b0d..101d4a90 100755 --- a/scripts/setup_pipecat.sh +++ b/scripts/setup_pipecat.sh @@ -20,6 +20,6 @@ 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] +pip install -e ./pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp,inworld,smallest] -echo "Setup complete! Pipecat is now available as a git submodule." \ No newline at end of file +echo "Setup complete! Pipecat is now available as a git submodule." diff --git a/scripts/setup_remote.sh b/scripts/setup_remote.sh index 919c881d..f1a0d149 100755 --- a/scripts/setup_remote.sh +++ b/scripts/setup_remote.sh @@ -135,10 +135,10 @@ if [[ -z "$FASTAPI_WORKERS" ]]; then if [[ -t 0 ]]; then echo "" echo -e "${YELLOW}Number of FastAPI workers (uvicorn processes nginx will load-balance):${NC}" - read -p "[4]: " FASTAPI_WORKERS - FASTAPI_WORKERS="${FASTAPI_WORKERS:-4}" + read -p "[2]: " FASTAPI_WORKERS + FASTAPI_WORKERS="${FASTAPI_WORKERS:-2}" else - FASTAPI_WORKERS="4" + FASTAPI_WORKERS="2" fi fi diff --git a/scripts/setup_requirements.ps1 b/scripts/setup_requirements.ps1 index 21f53592..b2106d8a 100644 --- a/scripts/setup_requirements.ps1 +++ b/scripts/setup_requirements.ps1 @@ -60,7 +60,7 @@ if ($Dev) { # Install pipecat in editable mode with all extras Write-Host "Installing pipecat dependencies..." -uv pip install -e './pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb]' +uv pip install -e './pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp,inworld,smallest]' if ($Dev) { Write-Host "Installing pipecat dev dependencies..." diff --git a/scripts/setup_requirements.sh b/scripts/setup_requirements.sh index f744a2f8..9a328c70 100755 --- a/scripts/setup_requirements.sh +++ b/scripts/setup_requirements.sh @@ -80,7 +80,7 @@ fi # Install pipecat in editable mode with all extras echo "Installing pipecat dependencies..." -uv pip install -e ./pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp] +uv pip install -e ./pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb,mcp,inworld,smallest] if [ "$DEV_MODE" -eq 1 ]; then echo "Installing pipecat dev dependencies..." diff --git a/scripts/update_remote.sh b/scripts/update_remote.sh index 119439b7..1dc75e71 100755 --- a/scripts/update_remote.sh +++ b/scripts/update_remote.sh @@ -71,10 +71,10 @@ if [[ -z "${FASTAPI_WORKERS:-}" ]]; then if [[ -t 0 ]]; then echo "" echo -e "${YELLOW}FASTAPI_WORKERS not set in .env. Number of uvicorn workers nginx will load-balance:${NC}" - read -p "[4]: " FASTAPI_WORKERS - FASTAPI_WORKERS="${FASTAPI_WORKERS:-4}" + read -p "[2]: " FASTAPI_WORKERS + FASTAPI_WORKERS="${FASTAPI_WORKERS:-2}" else - FASTAPI_WORKERS="4" + FASTAPI_WORKERS="2" fi fi diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index c502e1b1..e1c760af 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dograh-sdk" -version = "0.1.7" +version = "0.1.8" description = "Typed builder for Dograh voice-AI workflows" readme = "README.md" requires-python = ">=3.10" diff --git a/sdk/python/src/dograh_sdk/_generated_models.py b/sdk/python/src/dograh_sdk/_generated_models.py index 48a5a546..788b0634 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.mKgFDhNhca -# timestamp: 2026-06-09T10:10:10+00:00 +# filename: dograh-openapi-XXXXXX.json.6F33jkClt9 +# timestamp: 2026-06-19T12:41:10+00:00 from __future__ import annotations @@ -203,6 +203,8 @@ class GraphConstraints(BaseModel): max_incoming: Annotated[int | None, Field(title='Max Incoming')] = None min_outgoing: Annotated[int | None, Field(title='Min Outgoing')] = None max_outgoing: Annotated[int | None, Field(title='Max Outgoing')] = None + min_instances: Annotated[int | None, Field(title='Min Instances')] = None + max_instances: Annotated[int | None, Field(title='Max Instances')] = None class Method(Enum): diff --git a/sdk/python/src/dograh_sdk/typed/webhook.py b/sdk/python/src/dograh_sdk/typed/webhook.py index 305c0e46..0d1f0ba6 100644 --- a/sdk/python/src/dograh_sdk/typed/webhook.py +++ b/sdk/python/src/dograh_sdk/typed/webhook.py @@ -69,7 +69,7 @@ class Webhook(TypedNode): Additional HTTP headers to include with the request. """ - payload_template: dict[str, Any] = field(default_factory=lambda: {'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}}'}) + payload_template: dict[str, Any] = field(default_factory=lambda: {'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}}', 'user_recording_url': '{{user_recording_url}}', 'bot_recording_url': '{{bot_recording_url}}', 'transcript_url': '{{transcript_url}}'}) """ JSON body of the request. Values are Jinja-rendered against the run context — `{{workflow_run_id}}`, `{{gathered_context.foo}}`, diff --git a/sdk/typescript/package-lock.json b/sdk/typescript/package-lock.json index fa804ff4..987be493 100644 --- a/sdk/typescript/package-lock.json +++ b/sdk/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dograh/sdk", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dograh/sdk", - "version": "0.1.7", + "version": "0.1.8", "license": "BSD-2-Clause", "devDependencies": { "openapi-typescript": "^7.13.0", diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index a554f596..7c91ec7e 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@dograh/sdk", - "version": "0.1.7", + "version": "0.1.8", "description": "Typed builder for Dograh voice-AI workflows", "license": "BSD-2-Clause", "author": "Zansat Technologies Private Limited", diff --git a/sdk/typescript/src/_generated_models.ts b/sdk/typescript/src/_generated_models.ts index df205ddf..a3fb8e49 100644 --- a/sdk/typescript/src/_generated_models.ts +++ b/sdk/typescript/src/_generated_models.ts @@ -536,6 +536,10 @@ export interface components { min_outgoing?: number | null; /** Max Outgoing */ max_outgoing?: number | null; + /** Min Instances */ + min_instances?: number | null; + /** Max Instances */ + max_instances?: number | null; }; /** HTTPValidationError */ HTTPValidationError: { diff --git a/ui/.env.example b/ui/.env.example index 741790e5..13d914eb 100644 --- a/ui/.env.example +++ b/ui/.env.example @@ -1,3 +1,5 @@ BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_NODE_ENV=development +# form submissions backend +# NEXT_PUBLIC_ONBOARDING_API_URL=http://localhost:8001 diff --git a/ui/package-lock.json b/ui/package-lock.json index 9923bb0f..ffbc2882 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "ui", - "version": "1.30.1", + "version": "1.35.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ui", - "version": "1.30.1", + "version": "1.35.0", "dependencies": { "@dagrejs/dagre": "^1.1.4", "@radix-ui/react-alert-dialog": "^1.1.15", @@ -35,8 +35,8 @@ "next-themes": "^0.4.6", "pino": "^9.9.2", "pino-pretty": "^13.1.1", - "posthog-js": "^1.255.1", - "posthog-node": "^5.1.1", + "posthog-js": "^1.388.1", + "posthog-node": "^5.38.0", "react": "^19.1.0", "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", @@ -716,6 +716,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -917,7 +918,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "license": "MIT", - "peer": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1032,7 +1032,6 @@ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", @@ -1051,15 +1050,13 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@emotion/babel-plugin/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1069,7 +1066,6 @@ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", @@ -1082,22 +1078,19 @@ "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@emotion/memoize": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@emotion/react": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1122,7 +1115,6 @@ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", @@ -1135,22 +1127,19 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@emotion/unitless": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.8.0" } @@ -1159,15 +1148,13 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", @@ -2455,7 +2442,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2694,6 +2680,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2744,6 +2731,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", @@ -3415,6 +3403,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -3500,6 +3489,21 @@ "@oslojs/encoding": "1.0.0" } }, + "node_modules/@posthog/core": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.34.0.tgz", + "integrity": "sha512-hf+COAWSAG1UQHluPsfAOJDOR8tr8YY7cdf58ENGt20pEEWZ6sH3IbtvFDWkhp0ulSftA62x17w8R/f6IAwwEA==", + "license": "MIT", + "dependencies": { + "@posthog/types": "^1.387.0" + } + }, + "node_modules/@posthog/types": { + "version": "1.388.0", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.388.0.tgz", + "integrity": "sha512-BQO7e9ESdjOyKsCnoPoeHtq78ologEkPiemibsUlaAWC/Gk538jdTqyd0E1oSAlXnQqEANwjMn/A26vF2jljeQ==", + "license": "MIT" + }, "node_modules/@prisma/instrumentation": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.11.1.tgz", @@ -8122,6 +8126,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10005,6 +10010,7 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz", "integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=12.16" } @@ -10416,7 +10422,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -10427,7 +10432,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -10480,8 +10484,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/pg": { "version": "8.6.1", @@ -10508,6 +10511,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz", "integrity": "sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -10518,6 +10522,7 @@ "integrity": "sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -10527,7 +10532,6 @@ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -10557,6 +10561,13 @@ "@types/node": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -11050,7 +11061,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -11060,29 +11070,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -11093,15 +11099,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11114,7 +11118,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -11124,7 +11127,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -11133,15 +11135,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11158,7 +11158,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -11172,7 +11171,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11185,7 +11183,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -11200,7 +11197,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -11216,15 +11212,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@xyflow/react": { "version": "12.10.2", @@ -11291,6 +11285,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11312,7 +11307,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -11364,7 +11358,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -11382,7 +11375,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11398,8 +11390,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-colors": { "version": "4.1.3", @@ -11711,7 +11702,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -11839,6 +11829,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -12043,7 +12034,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -12302,7 +12292,6 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "license": "MIT", - "peer": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -12474,6 +12463,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -12816,12 +12806,20 @@ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", + "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -12904,7 +12902,6 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "license": "MIT", - "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -12913,8 +12910,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-abstract": { "version": "1.23.9", @@ -13034,8 +13030,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -13187,6 +13182,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -13360,6 +13356,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -13647,7 +13644,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -13753,8 +13749,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { "version": "1.1.4", @@ -13836,8 +13831,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -14089,8 +14083,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "14.0.0", @@ -14304,6 +14297,7 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -14913,7 +14907,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -14928,7 +14921,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -15018,8 +15010,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -15351,15 +15342,13 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -15440,15 +15429,13 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", @@ -15479,7 +15466,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -15489,7 +15475,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -15587,14 +15572,14 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next": { "version": "15.5.14", "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.14", "@swc/helpers": "0.5.15", @@ -16022,7 +16007,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -16097,7 +16081,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -16338,36 +16321,39 @@ } }, "node_modules/posthog-js": { - "version": "1.255.1", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.255.1.tgz", - "integrity": "sha512-KMh0o9MhORhEZVjXpktXB5rJ8PfDk+poqBoTSoLzWgNjhJf6D8jcyB9jUMA6vVPfn4YeepVX5NuclDRqOwr5Mw==", + "version": "1.388.1", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.388.1.tgz", + "integrity": "sha512-3WaTv0LPWRx3hT3frBd4Z7fMZcqPS4St0otB+AGO6ixOFc2KCTGSIJwujtArC2rraWS3UPlAYVf7QaA0Fgy+MA==", "license": "SEE LICENSE IN LICENSE", "dependencies": { + "@posthog/core": "^1.34.0", + "@posthog/types": "^1.388.0", "core-js": "^3.38.1", + "dompurify": "^3.3.2", "fflate": "^0.4.8", - "preact": "^10.19.3", - "web-vitals": "^4.2.4" - }, - "peerDependencies": { - "@rrweb/types": "2.0.0-alpha.17", - "rrweb-snapshot": "2.0.0-alpha.17" - }, - "peerDependenciesMeta": { - "@rrweb/types": { - "optional": true - }, - "rrweb-snapshot": { - "optional": true - } + "preact": "^10.28.2", + "query-selector-shadow-dom": "^1.0.1", + "web-vitals": "^5.1.0" } }, "node_modules/posthog-node": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.1.1.tgz", - "integrity": "sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==", + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.38.0.tgz", + "integrity": "sha512-xEojUWq7ajYfsMPZIexG9xtvhewpqrPWvL/qRhnm8W3KVZi6BlXHe29tjywZJglrDwEQpqdtYKbRXyg2zF9MWw==", "license": "MIT", + "dependencies": { + "@posthog/core": "^1.33.0" + }, "engines": { - "node": ">=20" + "node": "^20.20.0 || >=22.22.0" + }, + "peerDependencies": { + "rxjs": "^7.0.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } } }, "node_modules/powershell-utils": { @@ -16488,6 +16474,12 @@ "node": ">=10.13.0" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -16531,6 +16523,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -16561,6 +16554,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -16587,6 +16581,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.1.tgz", "integrity": "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -16611,13 +16606,15 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -16757,7 +16754,6 @@ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -16823,7 +16819,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -16861,8 +16858,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", @@ -16899,7 +16895,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16984,6 +16979,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -17155,7 +17151,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -17192,7 +17187,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -17204,8 +17198,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/secure-json-parse": { "version": "4.0.0", @@ -17761,8 +17754,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/supports-color": { "version": "7.2.0", @@ -17802,7 +17794,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz", "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -17831,7 +17824,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -17850,7 +17842,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -17883,8 +17874,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/thread-stream": { "version": "3.1.0", @@ -17961,6 +17951,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -18149,6 +18140,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18335,7 +18327,6 @@ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -18422,7 +18413,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -18432,9 +18422,9 @@ } }, "node_modules/web-vitals": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", - "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.3.0.tgz", + "integrity": "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==", "license": "Apache-2.0" }, "node_modules/webidl-conversions": { @@ -18448,7 +18438,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -18512,7 +18501,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -18526,7 +18514,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -18704,7 +18691,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", - "peer": true, "engines": { "node": ">= 6" } @@ -18813,6 +18799,7 @@ "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", "license": "MIT", + "peer": true, "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", @@ -18855,6 +18842,7 @@ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", "license": "MIT", + "peer": true, "engines": { "node": ">=12.20.0" }, diff --git a/ui/package.json b/ui/package.json index 5e0a3ca9..71742dfc 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "ui", - "version": "1.35.0", + "version": "1.37.0", "private": true, "scripts": { "dev": "cross-env NODE_OPTIONS=--enable-source-maps next dev --turbopack", @@ -9,7 +9,8 @@ "lint": "next lint", "fix-lint": "npx eslint --fix . --ignore-pattern '.next/*' --ignore-pattern 'node_modules/*' --ignore-pattern 'next-env.d.ts'", "generate-client": "openapi-ts", - "test:display-options": "node scripts/test-display-options.mts" + "test:display-options": "node scripts/test-display-options.mts", + "lint:lead-flow": "bash ../../user_onboarding/scripts/check_lead_flow.sh" }, "dependencies": { "@dagrejs/dagre": "^1.1.4", @@ -39,8 +40,8 @@ "next-themes": "^0.4.6", "pino": "^9.9.2", "pino-pretty": "^13.1.1", - "posthog-js": "^1.255.1", - "posthog-node": "^5.1.1", + "posthog-js": "^1.388.1", + "posthog-node": "^5.38.0", "react": "^19.1.0", "react-day-picker": "^9.8.0", "react-dom": "^19.1.0", diff --git a/ui/public/brand-imprint-dark.svg b/ui/public/brand-imprint-dark.svg new file mode 100644 index 00000000..6d44ab46 --- /dev/null +++ b/ui/public/brand-imprint-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/brand-imprint-light.svg b/ui/public/brand-imprint-light.svg new file mode 100644 index 00000000..a22a0c3b --- /dev/null +++ b/ui/public/brand-imprint-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/public/dograh-logo-inverse.png b/ui/public/dograh-logo-inverse.png new file mode 100755 index 00000000..8415e795 Binary files /dev/null and b/ui/public/dograh-logo-inverse.png differ diff --git a/ui/public/dograh-logo.png b/ui/public/dograh-logo.png new file mode 100755 index 00000000..40165da8 Binary files /dev/null and b/ui/public/dograh-logo.png differ diff --git a/ui/public/dograh-mark.png b/ui/public/dograh-mark.png new file mode 100755 index 00000000..328f51f1 Binary files /dev/null and b/ui/public/dograh-mark.png differ diff --git a/ui/public/embed/dograh-widget.js b/ui/public/embed/dograh-widget.js index 9a266208..65614cf7 100644 --- a/ui/public/embed/dograh-widget.js +++ b/ui/public/embed/dograh-widget.js @@ -26,10 +26,12 @@ stream: null, sessionToken: null, workflowRunId: null, + pcId: null, connectionStatus: 'idle', // idle, connecting, connected, failed audioElement: null, turnCredentials: null, // TURN server credentials callStartedAt: null, // Timestamp when call connected (for duration tracking) + gracefulDisconnect: false, callbacks: { onReady: null, onCallStart: null, @@ -611,6 +613,7 @@ * Start voice call */ async function startCall() { + state.gracefulDisconnect = false; updateStatus('connecting', 'Connecting...', 'Please wait while we establish the connection'); if (state.callbacks.onCallStart) { @@ -630,6 +633,11 @@ // Request microphone permission try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + // Release any stream still held from a prior attempt before retaining + // the new one, so a re-entrant start can't leak the microphone. + if (state.stream) { + state.stream.getTracks().forEach(track => track.stop()); + } state.stream = stream; } catch (micError) { // Handle specific microphone permission errors @@ -657,6 +665,31 @@ } catch (error) { console.error('Dograh Widget: Failed to start call', error); + + // Release anything acquired before the failure so a retry starts clean. + // getUserMedia may have succeeded before a later step (WebSocket / + // negotiation) threw, which would otherwise leave the mic held and block + // the next getUserMedia(). Null the refs before close() so the peer/ws + // state handlers short-circuit instead of re-entering teardown. + if (state.stream) { + state.stream.getTracks().forEach(track => track.stop()); + state.stream = null; + } + if (state.pc) { + const pc = state.pc; + state.pc = null; + if (pc.signalingState !== 'closed') { + pc.close(); + } + } + if (state.ws) { + const ws = state.ws; + state.ws = null; + if (ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) { + ws.close(); + } + } + updateStatus('failed', 'Connection failed', error.message || 'Please check your microphone and try again'); // Trigger error callback @@ -766,45 +799,69 @@ }; // Monitor connection state - state.pc.oniceconnectionstatechange = () => { - console.log('ICE connection state:', state.pc.iceConnectionState); + state.pc.oniceconnectionstatechange = handlePeerConnectionStateChange; + state.pc.onconnectionstatechange = handlePeerConnectionStateChange; + state.pc.onicecandidate = sendIceCandidate; + } - if (state.pc.iceConnectionState === 'connected' || state.pc.iceConnectionState === 'completed') { - const wasAlreadyConnected = state.callStartedAt !== null; - updateStatus('connected', 'Connected', 'Your voice call is now active'); - if (!wasAlreadyConnected) { - state.callStartedAt = Date.now(); - if (state.callbacks.onCallConnected) { - state.callbacks.onCallConnected({ - agentId: state.config.workflowId || null, - token: state.config.token || null, - workflowRunId: state.workflowRunId || null - }); - } + function handlePeerConnectionStateChange() { + const pc = state.pc; + if (!pc) return; + + console.log('Peer connection state:', pc.connectionState, 'ICE:', pc.iceConnectionState); + + if (pc.connectionState === 'connected' || pc.iceConnectionState === 'connected' || pc.iceConnectionState === 'completed') { + const wasAlreadyConnected = state.callStartedAt !== null; + updateStatus('connected', 'Connected', 'Your voice call is now active'); + if (!wasAlreadyConnected) { + state.callStartedAt = Date.now(); + if (state.callbacks.onCallConnected) { + state.callbacks.onCallConnected({ + agentId: state.config.workflowId || null, + token: state.config.token || null, + workflowRunId: state.workflowRunId || null + }); } - } else if (state.pc.iceConnectionState === 'failed' || state.pc.iceConnectionState === 'disconnected') { - updateStatus('failed', 'Connection lost', 'The call has been disconnected'); - stopCall(); } - }; + return; + } + if (pc.connectionState === 'failed' || pc.iceConnectionState === 'failed') { + stopCall({ + graceful: false, + status: 'failed', + text: 'Connection lost', + subtext: 'The call has been disconnected' + }); + return; + } + + if ( + pc.connectionState === 'closed' || + pc.connectionState === 'disconnected' || + pc.iceConnectionState === 'closed' || + pc.iceConnectionState === 'disconnected' + ) { + stopCall({ graceful: true }); + } + } + + function sendIceCandidate(event) { // Handle ICE candidates for trickling - state.pc.onicecandidate = (event) => { - if (state.ws && state.ws.readyState === WebSocket.OPEN) { - const message = { - type: 'ice-candidate', - payload: { - candidate: event.candidate ? { - candidate: event.candidate.candidate, - sdpMid: event.candidate.sdpMid, - sdpMLineIndex: event.candidate.sdpMLineIndex - } : null, - pc_id: state.pcId - } - }; - state.ws.send(JSON.stringify(message)); - } - }; + if (state.ws && state.ws.readyState === WebSocket.OPEN) { + const message = { + type: 'ice-candidate', + payload: { + candidate: event.candidate ? { + candidate: event.candidate.candidate, + sdpMid: event.candidate.sdpMid, + sdpMLineIndex: event.candidate.sdpMLineIndex + } : null, + pc_id: state.pcId + } + }; + state.ws.send(JSON.stringify(message)); + } } /** @@ -828,9 +885,16 @@ reject(error); }; - state.ws.onclose = () => { + state.ws.onclose = (event) => { console.log('WebSocket closed'); - if (state.connectionStatus === 'connected') { + state.ws = null; + + if (event.reason === 'call ended') { + stopCall({ graceful: true, closeWebSocket: false }); + return; + } + + if (state.connectionStatus === 'connected' && !state.gracefulDisconnect) { updateStatus('failed', 'Connection lost', 'The call has been disconnected'); } }; @@ -882,6 +946,11 @@ updateStatus('failed', 'Server error', message.payload.message || 'An error occurred'); break; + case 'call-ended': + console.log('Call ended by server:', message.payload); + stopCall({ graceful: true }); + break; + default: console.warn('Unknown message type:', message.type); } @@ -913,7 +982,15 @@ /** * Stop voice call */ - function stopCall() { + function stopCall(options = {}) { + const graceful = options.graceful !== false; + const closeWebSocket = options.closeWebSocket !== false; + const status = options.status || 'idle'; + const text = options.text || 'Call ended'; + const subtext = options.subtext || 'Click below to start a new call'; + + state.gracefulDisconnect = graceful; + // Fire onCallDisconnected only if the call had actually connected, with // identifiers and duration. Must run before we clear callStartedAt. if (state.callStartedAt && state.callbacks.onCallDisconnected) { @@ -927,15 +1004,20 @@ } state.callStartedAt = null; - updateStatus('idle', 'Call ended', 'Click below to start a new call'); + updateStatus(status, text, subtext); if (state.callbacks.onCallEnd) { state.callbacks.onCallEnd(); } // Close WebSocket - if (state.ws) { - state.ws.close(); + if (closeWebSocket && state.ws) { + const ws = state.ws; + state.ws = null; + if (ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) { + ws.close(); + } + } else if (!closeWebSocket) { state.ws = null; } @@ -947,8 +1029,11 @@ // Close peer connection if (state.pc) { - state.pc.close(); + const pc = state.pc; state.pc = null; + if (pc.signalingState !== 'closed') { + pc.close(); + } } // Clear audio diff --git a/ui/src/app/api-keys/page.tsx b/ui/src/app/api-keys/page.tsx index 2558889b..05aa7c33 100644 --- a/ui/src/app/api-keys/page.tsx +++ b/ui/src/app/api-keys/page.tsx @@ -304,7 +304,7 @@ export default function APIKeysPage() { // Don't render content until auth is loaded if (loading || !user) { return ( -
+
@@ -319,7 +319,7 @@ export default function APIKeysPage() { const showServiceKeyArchiveControls = !isOSS; return ( -
+
diff --git a/ui/src/app/api/config/auth/route.ts b/ui/src/app/api/config/auth/route.ts index a3b33477..cf6553f3 100644 --- a/ui/src/app/api/config/auth/route.ts +++ b/ui/src/app/api/config/auth/route.ts @@ -1,10 +1,17 @@ import { NextResponse } from 'next/server'; -import { getAuthProvider } from '@/lib/auth/config'; +import { getAuthProvider, getStackConfig } from '@/lib/auth/config'; import logger from '@/lib/logger'; export async function GET() { const provider = await getAuthProvider(); + // When using Stack, hand the public client config to the browser so it can + // initialize the Stack SDK at runtime (no build-time NEXT_PUBLIC_* needed). + const stackConfig = provider === 'stack' ? await getStackConfig() : null; logger.debug(`Got provider ${provider} from getAuthProvider`) - return NextResponse.json({ provider }); + return NextResponse.json({ + provider, + stackProjectId: stackConfig?.projectId ?? null, + stackPublishableClientKey: stackConfig?.publishableClientKey ?? null, + }); } diff --git a/ui/src/app/auth/login/page.tsx b/ui/src/app/auth/login/page.tsx index 39c6ceb1..a1fef886 100644 --- a/ui/src/app/auth/login/page.tsx +++ b/ui/src/app/auth/login/page.tsx @@ -5,8 +5,9 @@ import { useState } from "react"; import { toast } from "sonner"; import { loginApiV1AuthLoginPost } from "@/client/sdk.gen"; +import { AuthEnterpriseCTA } from "@/components/auth/AuthEnterpriseCTA"; +import { AuthShell } from "@/components/auth/AuthShell"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -46,48 +47,48 @@ export default function LoginPage() { }; return ( -
- - - Sign in - Enter your email and password to continue - - -
-
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - /> -
- -
-

- Don't have an account?{" "} - - Sign up - -

-
-
-
+ }> +
+

Sign in

+

+ Enter your email and password to continue +

+
+ +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ +
+ +

+ Don't have an account?{" "} + + Sign up + +

+
); } diff --git a/ui/src/app/auth/signup/page.tsx b/ui/src/app/auth/signup/page.tsx index d9d98c18..ad43ec83 100644 --- a/ui/src/app/auth/signup/page.tsx +++ b/ui/src/app/auth/signup/page.tsx @@ -5,8 +5,9 @@ import { useState } from "react"; import { toast } from "sonner"; import { signupApiV1AuthSignupPost } from "@/client/sdk.gen"; +import { AuthEnterpriseCTA } from "@/components/auth/AuthEnterpriseCTA"; +import { AuthShell } from "@/components/auth/AuthShell"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -58,61 +59,59 @@ export default function SignupPage() { }; return ( -
- - - Create an account - Enter your details to get started - - -
-
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - minLength={8} - /> -
-
- - setConfirmPassword(e.target.value)} - required - minLength={8} - /> -
- -
-

- Already have an account?{" "} - - Sign in - -

-
-
-
+ }> +
+

Create an account

+

Enter your details to get started

+
+ +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + minLength={8} + /> +
+
+ + setConfirmPassword(e.target.value)} + required + minLength={8} + /> +
+ +
+ +

+ Already have an account?{" "} + + Sign in + +

+
); } diff --git a/ui/src/app/billing/page.tsx b/ui/src/app/billing/page.tsx index 0a9732c9..9ac8a497 100644 --- a/ui/src/app/billing/page.tsx +++ b/ui/src/app/billing/page.tsx @@ -5,6 +5,8 @@ import { ChevronRight, CircleDollarSign, CreditCard, + ExternalLink, + Info, RefreshCw, } from "lucide-react"; import Link from "next/link"; @@ -124,7 +126,8 @@ export default function BillingPage() { ); const isBillingV2 = credits?.billing_version === "v2"; - const canPurchaseCredits = isBillingV2 && config?.deploymentMode !== "oss"; + const isOssMode = config?.deploymentMode === "oss"; + const canPurchaseCredits = isBillingV2 && !isOssMode; const totalQuota = credits?.total_quota ?? 0; const remainingCredits = credits?.remaining_credits ?? 0; const usedCredits = credits?.total_credits_used ?? 0; @@ -265,6 +268,36 @@ export default function BillingPage() {
+ {isOssMode && ( +
+ +
+

Credit purchases are unavailable in OSS mode

+

+ You can't purchase credits from this self-hosted app. Sign up and + purchase credits at{" "} + + app.dograh.com + + + . Then add the generated service key in{" "} + + Model Configurations + + . Usage for that service key is visible in app.dograh.com. +

+
+
+ )} +
diff --git a/ui/src/app/campaigns/new/page.tsx b/ui/src/app/campaigns/new/page.tsx index aab2adcf..c6499b4c 100644 --- a/ui/src/app/campaigns/new/page.tsx +++ b/ui/src/app/campaigns/new/page.tsx @@ -253,7 +253,7 @@ export default function NewCampaignPage() { } if (maxConcurrencyValue > effectiveLimit) { if (availableFromNumbersCount > 0 && availableFromNumbersCount < orgConcurrentLimit) { - toast.error(`Max concurrent calls cannot exceed ${effectiveLimit}. The selected configuration has ${availableFromNumbersCount} phone number(s) — add more CLIs to increase concurrency.`); + toast.error(`Max concurrent calls cannot exceed ${effectiveLimit}. The selected configuration has ${availableFromNumbersCount} phone number(s) - add more CLIs to increase concurrency.`); } else { toast.error(`Max concurrent calls cannot exceed organization limit (${effectiveLimit})`); } @@ -455,7 +455,7 @@ export default function NewCampaignPage() { value={config.id.toString()} > {config.name} ({config.provider}) - {config.is_default_outbound ? ' — default' : ''} + {config.is_default_outbound ? ' - default' : ''} )) )} diff --git a/ui/src/app/globals.css b/ui/src/app/globals.css index 0c7bdf9c..cc929a23 100644 --- a/ui/src/app/globals.css +++ b/ui/src/app/globals.css @@ -42,6 +42,8 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + --color-cta: var(--cta); + --color-cta-foreground: var(--cta-foreground); } :root { @@ -77,6 +79,14 @@ --sidebar-ring: oklch(0.708 0 0); --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); + /* Single restrained warm accent — used only on primary CTAs + focus rings. */ + --cta: oklch(0.72 0.15 65); + --cta-foreground: oklch(0.16 0.02 60); + /* Giant faded "dograh" wordmark (authentic Proxima Nova letterforms traced + from the brand logo PNG — the font is commercial, so the lettering ships + as static artwork in /public; fill + 0.9% opacity are baked into the + files). Theme-switched here; consumed by .app-surface and .auth-imprint. */ + --brand-imprint: url("/brand-imprint-light.svg"); } .dark { @@ -111,6 +121,10 @@ --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); + /* Warm accent, slightly brighter against the near-black surfaces. */ + --cta: oklch(0.78 0.16 67); + --cta-foreground: oklch(0.16 0.02 60); + --brand-imprint: url("/brand-imprint-dark.svg"); } @layer base { @@ -135,3 +149,177 @@ .animate-spin-slow { animation: spin-slow 3s linear infinite; } + +@layer components { + /* CSS-only audio-waveform motif for the auth brand panel. A row of bars that + breathe at staggered intervals, evoking live voice. Decorative only. */ + .auth-waveform { + display: flex; + align-items: center; + gap: 0.3rem; + height: 3.5rem; + } + + .auth-waveform span { + display: block; + width: 0.25rem; + border-radius: 9999px; + background: linear-gradient( + to top, + color-mix(in oklch, var(--cta) 70%, transparent), + color-mix(in oklch, var(--cta) 25%, transparent) + ); + animation: auth-wave 1.4s ease-in-out infinite; + transform-origin: center; + } + + .auth-waveform span:nth-child(1) { animation-delay: 0s; height: 35%; } + .auth-waveform span:nth-child(2) { animation-delay: 0.15s; height: 65%; } + .auth-waveform span:nth-child(3) { animation-delay: 0.3s; height: 100%; } + .auth-waveform span:nth-child(4) { animation-delay: 0.45s; height: 55%; } + .auth-waveform span:nth-child(5) { animation-delay: 0.6s; height: 80%; } + .auth-waveform span:nth-child(6) { animation-delay: 0.3s; height: 45%; } + .auth-waveform span:nth-child(7) { animation-delay: 0.15s; height: 70%; } + .auth-waveform span:nth-child(8) { animation-delay: 0s; height: 30%; } + + @keyframes auth-wave { + 0%, 100% { transform: scaleY(0.4); opacity: 0.7; } + 50% { transform: scaleY(1); opacity: 1; } + } + + @media (prefers-reduced-motion: reduce) { + .auth-waveform span { animation: none; } + } + + /* Matte app background — flat charcoal (dark) / soft paper (light), NO + gradients, with one subtle graphic in BOTH themes: the giant faded + "dograh" wordmark (--brand-imprint, defined in :root/.dark) pinned to + the bottom of the viewport, echoing the dograh.com footer. */ + /* NOTE: background-attachment: fixed positions in VIEWPORT space but only + paints inside .app-surface, which starts right of the ~270px sidebar — + the +135px x-shift recentres the wordmark on the VISIBLE canvas. */ + .app-surface { + background-color: oklch(0.984 0.001 80); + background-image: var(--brand-imprint); + background-size: min(68vw, 980px) auto; + background-position: calc(50% + 135px) calc(100% - 24px); + background-repeat: no-repeat; + background-attachment: fixed; + } + /* Sidebar is offcanvas on small screens — true centre there. */ + @media (max-width: 767px) { + .app-surface { + background-position: center calc(100% - 24px); + } + } + .dark .app-surface { + background-color: oklch(0.165 0.002 80); + } + + /* Giant faded "dograh" imprint for the auth pages (applied to the AuthShell + form column, shared by Stack + OSS login/signup). Same --brand-imprint as + .app-surface; element-relative here (no fixed attachment), so it centers + and scales to whatever element carries the class. */ + .auth-imprint { + background-image: var(--brand-imprint); + background-size: min(86%, 920px) auto; + background-position: center calc(100% - 32px); + background-repeat: no-repeat; + } + +} + +/* --------------------------------------------------------------------------- + UN-LAYERED overrides. These intentionally live OUTSIDE @layer blocks: + they restyle elements that carry Tailwind utility classes (bg-sidebar, + rounded-lg, shadow-sm, border-*) and utilities sit in a later cascade + layer than @layer components — un-layered author CSS beats both. +--------------------------------------------------------------------------- */ + +/* Floating-dock sidebar: detached rounded panel. Targets the shadcn sidebar's + inner panel; applied via .app-sidebar-dock on . */ +.app-sidebar-dock [data-slot="sidebar-inner"] { + border-radius: 1.25rem; + overflow: hidden; +} +/* Flat carbon-charcoal panel with a soft light glow along the LEFT edge: + a 1px highlight line plus an inner bloom fading rightwards. */ +.dark .app-sidebar-dock [data-slot="sidebar-inner"] { + border-color: rgb(255 255 255 / 0.1); + background-color: oklch(0.18 0.002 80); + box-shadow: + inset 1px 0 0 rgb(255 255 255 / 0.1), + inset 3px 0 6px -4px rgb(255 255 255 / 0.08), + 0 24px 50px -14px rgb(0 0 0 / 0.85); +} + +/* Card surface ("Crosshatch + Top-Lit Edge", user-approved 2026-06-11 after a + 3-round design board): a 45° hairline twill weave at 1% laid over the panel + colour, plus — dark mode only — a brighter SOLID top border, like light + catching the machined top edge of the panel. Applied app-wide by the Card + primitive (components/ui/card.tsx). Un-layered so border-top-color beats + the border-border/60 utility. No gradients (user constraint). */ +.card-weave { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='M0 0l12 12M12 0L0 12' stroke='%23000000' stroke-opacity='.015' fill='none'/%3E%3C/svg%3E"); + background-repeat: repeat; +} +.dark .card-weave { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='M0 0l12 12M12 0L0 12' stroke='%23ffffff' stroke-opacity='.01' fill='none'/%3E%3C/svg%3E"); + border-top-color: rgb(255 255 255 / 0.2); +} + +/* Lead-form shell ("Ledger" treatment, user-approved 2026-06-11): neutral + charcoal slab where ONLY the header band is darker (body and footer share + the slab colour), muted compact labels, and underline-only fields with an + amber underline on focus. Applied by LeadModalShell; CaptchaChallenge + reuses slab + underline. */ +.dark .lead-form-slab { + background-color: oklch(0.215 0 0); + border-color: rgb(255 255 255 / 0.1); +} +/* Muted, compact labels — the big white default labels read amateurish. */ +.lead-form-underline label { + font-size: 0.8125rem; + font-weight: 500; + color: var(--muted-foreground); +} +/* Ghost placeholders: present on every field, but barely-there. */ +.lead-form-underline :is(input, textarea)::placeholder { + color: var(--muted-foreground); + opacity: 0.14; +} +.lead-form-underline [data-slot="select-trigger"][data-placeholder] { + color: color-mix(in oklab, var(--muted-foreground) 17%, transparent); +} +/* Underline-only fields: transparent box, hairline bottom border, amber + underline on the focused control. Compact heights keep rows tight. */ +.lead-form-underline :is(input, textarea, [data-slot="select-trigger"]) { + background-color: transparent; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid var(--border); + border-radius: 0; + box-shadow: none; + padding-left: 2px; + padding-right: 2px; +} +.lead-form-underline :is(input, [data-slot="select-trigger"]) { + height: 2.125rem; +} +.lead-form-underline textarea { + min-height: 3.25rem; +} +/* The phone country selector ships its own box — flatten it to match. */ +.lead-form-underline .react-international-phone-country-selector-button { + border: 0 !important; + border-bottom: 1px solid var(--border) !important; + border-radius: 0 !important; + background: transparent !important; +} +.lead-form-underline :is(input, textarea, [data-slot="select-trigger"]):focus-visible, +.lead-form-underline [data-slot="select-trigger"][data-state="open"] { + outline: none; + box-shadow: none; + border-bottom-color: var(--cta); +} diff --git a/ui/src/app/handler/[...stack]/BackButton.tsx b/ui/src/app/handler/[...stack]/BackButton.tsx index f2d730ae..5827c8ef 100644 --- a/ui/src/app/handler/[...stack]/BackButton.tsx +++ b/ui/src/app/handler/[...stack]/BackButton.tsx @@ -8,17 +8,26 @@ import { Button } from "@/components/ui/button"; export function BackButton() { const router = useRouter(); + // On a direct load (e.g. an OAuth redirect or a deep link to /handler/sign-in) + // there's no in-app history, so router.back() would bounce the user off-app. + // Fall back to the home route in that case. + const handleBack = () => { + if (typeof window !== "undefined" && window.history.length > 1) { + router.back(); + } else { + router.push("/"); + } + }; + return ( -
- -
+ ); } diff --git a/ui/src/app/handler/[...stack]/page.tsx b/ui/src/app/handler/[...stack]/page.tsx index 2ae94489..a862d5ef 100644 --- a/ui/src/app/handler/[...stack]/page.tsx +++ b/ui/src/app/handler/[...stack]/page.tsx @@ -1,18 +1,41 @@ -import { StackHandler } from "@stackframe/stack"; +import { StackHandler, StackTheme } from "@stackframe/stack"; +import { AuthEnterpriseCTA } from "@/components/auth/AuthEnterpriseCTA"; +import { AuthShell } from "@/components/auth/AuthShell"; import { getAuthProvider } from "@/lib/auth/config"; import { BackButton } from "./BackButton"; +import { stackAuthDarkTheme } from "./stack-theme"; + +// Stack Auth serves every auth page from this one catch-all. We give the brand +// split-screen shell to the user-facing FORM routes and render only the wide / +// interstitial "machine" routes full-page (so account-settings etc. aren't +// cramped into the narrow auth card). This is a BLOCKLIST, not an allowlist, so +// new or aliased form routes — Stack's `log-in`/`register` aliases, case/dash +// variants, email-verification, mfa, team-invitation — get the shell by default. +// Matching is normalized (lowercase, dashes stripped) to mirror Stack's own +// case- and dash-insensitive route resolution. +const FULL_PAGE_ROUTES = new Set([ + "accountsettings", + "oauthcallback", + "magiclinkcallback", + "signout", + "error", +]); export default async function Handler(props: unknown) { const authProvider = await getAuthProvider(); if (authProvider === "local") { return ( -
-

Local Auth Mode

-

Stack Auth handler is disabled when using local authentication.

-
+ }> +
+

Local Auth Mode

+

+ Stack Auth handler is disabled when using local authentication. +

+
+
); } @@ -20,16 +43,35 @@ export default async function Handler(props: unknown) { const { getStackServerApp } = await import("@/lib/auth/server"); const app = await getStackServerApp(); - return ( -
- -
- -
-
+ // Resolve the first route segment to decide layout. `params` is async in + // Next 15; awaiting it here does not consume it for StackHandler below. + let segment = ""; + try { + const { params } = props as { params?: Promise<{ stack?: string[] }> }; + const resolved = params ? await params : undefined; + segment = resolved?.stack?.[0] ?? ""; + } catch { + segment = ""; + } + const normalizedSegment = segment.toLowerCase().replace(/-/g, ""); + const isAuthForm = segment !== "" && !FULL_PAGE_ROUTES.has(normalizedSegment); + const showBackButton = !new Set(["signin", "login"]).has(normalizedSegment); + + const handler = ( + + + ); + + if (isAuthForm) { + return ( + }> + {showBackButton && } + {handler} + + ); + } + + // account-settings and machine routes render full-page (Stack's own layout). + return handler; } diff --git a/ui/src/app/handler/[...stack]/stack-theme.ts b/ui/src/app/handler/[...stack]/stack-theme.ts new file mode 100644 index 00000000..8d21be17 --- /dev/null +++ b/ui/src/app/handler/[...stack]/stack-theme.ts @@ -0,0 +1,34 @@ +// Dark token overrides for the embedded Stack Auth form so it blends into the +// auth card surface (zinc-900 background, zinc-100 foreground, the warm CTA +// accent on the primary button, zinc-800 borders/inputs). Stack's theme parser +// does not accept OKLCH strings, so keep these values in hex. + +import type { StackTheme } from "@stackframe/stack"; +import type { ComponentProps } from "react"; + +type ThemeConfig = NonNullable["theme"]>; + +export const stackAuthDarkTheme: ThemeConfig = { + dark: { + background: "#27272a", + foreground: "#fafafa", + card: "#27272a", + cardForeground: "#fafafa", + popover: "#27272a", + popoverForeground: "#fafafa", + primary: "#fbbf24", + primaryForeground: "#422006", + secondary: "#3f3f46", + secondaryForeground: "#fafafa", + muted: "#3f3f46", + mutedForeground: "#a1a1aa", + accent: "#3f3f46", + accentForeground: "#fafafa", + destructive: "#ef4444", + destructiveForeground: "#fafafa", + border: "#3f3f46", + input: "#3f3f46", + ring: "#fbbf24", + }, + radius: "0.625rem", +}; diff --git a/ui/src/app/impersonate/route.ts b/ui/src/app/impersonate/route.ts index c1119d29..421d995b 100644 --- a/ui/src/app/impersonate/route.ts +++ b/ui/src/app/impersonate/route.ts @@ -1,5 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; +import { getStackConfig } from "@/lib/auth/config"; + /** * Helper route that receives a refresh token via query parameters, stores it as * the regular Stack cookie *for the current sub-domain only* and finally @@ -18,6 +20,13 @@ export async function GET(request: NextRequest) { return new Response("Missing refresh_token", { status: 400 }); } + // The Stack session cookie is named `stack-refresh-`. The project + // id comes from the backend at runtime, so no inlined NEXT_PUBLIC_* is needed. + const stackConfig = await getStackConfig(); + if (!stackConfig) { + return new Response("Stack auth is not configured", { status: 400 }); + } + // Prepare redirect – if the supplied redirect path is an absolute URL we use // it as-is, otherwise we resolve it relative to the current request. const redirectUrl = redirectPath.startsWith("http") @@ -32,7 +41,7 @@ export async function GET(request: NextRequest) { // Store the refresh token cookie without an explicit domain so that it is // scoped to the current (sub-)domain. This avoids collisions between the // admin (superadmin.*) and the regular app (app.*) domains. - response.cookies.set(`stack-refresh-${process.env.NEXT_PUBLIC_STACK_PROJECT_ID}` as string, refreshToken, { + response.cookies.set(`stack-refresh-${stackConfig.projectId}`, refreshToken, { path: "/", maxAge, secure: true, diff --git a/ui/src/app/layout.tsx b/ui/src/app/layout.tsx index 9b346354..15b4cfb5 100644 --- a/ui/src/app/layout.tsx +++ b/ui/src/app/layout.tsx @@ -9,6 +9,7 @@ import AppLayout from "@/components/layout/AppLayout"; import PostHogIdentify from "@/components/PostHogIdentify"; import { SentryErrorBoundary } from "@/components/SentryErrorBoundary"; import SpinLoader from "@/components/SpinLoader"; +import { ThemeProvider } from "@/components/ThemeProvider"; import { Toaster } from "@/components/ui/sonner"; import { AppConfigProvider } from "@/context/AppConfigContext"; import { OnboardingProvider } from "@/context/OnboardingContext"; @@ -39,21 +40,24 @@ export default function RootLayout({ }) { return ( - + - {/* Inline script to prevent flash of light theme - runs before React hydrates */} + {/* Inline script to prevent flash of light theme - runs before React hydrates. + Dark is the locked default: only an explicit stored 'light' opts out. */}