# Tough Tongue AI > Voice AI agent platform. Create conversational agents, embed them anywhere, collect session data and analysis programmatically. ## What It Does Tough Tongue AI lets you **create voice AI agents** and deploy them via iframe, phone call, or meeting bot. Each agent interaction produces a **session** with transcript, evaluation scores, and extracted variables — all retrievable via API. **Platform capabilities:** - Agents use tools mid-conversation: images, slides, diagrams, whiteboards, knowledge base search - Multimodal: reads tone, facial expressions, body language (not just words) - Post-session: auto-analysis with rubric scoring, variable extraction, report cards ## Integration Quick Start 1. Get API token → `https://app.toughtongueai.com/developer` 2. Create a scenario (API or UI) → defines agent behavior 3. Deploy: embed iframe, schedule phone call, or dispatch meeting bot 4. Collect results: session transcripts, scores, extracted data via API 5. React in real-time: webhooks notify your server on session lifecycle events ## Three Agent Types | Type | What it is | Best for | | ------------ | -------------------------------------------- | ---------------------------------------- | | **Scenario** | One-off conversation agent | Interviews, negotiations, customer calls | | **Coach** | Persistent agent with memory across sessions | Ongoing training, accountability | | **Course** | Ordered sequence of scenarios + content | Structured learning programs | ## Deployment Channels | Channel | How | API | | --------------- | ---------------------------------- | ----------------------------- | | **Iframe** | Embed in any webpage | URL-based, no API call needed | | **Phone (SIP)** | Agent calls a phone number | `POST /v2/sip/call` | | **Phone Batch** | Agent calls a list of numbers | `POST /v2/sip/batch` | | **Meeting Bot** | Agent joins Google Meet/Zoom/Teams | `POST /v2/meeting-bots` | ## Key Links - Platform: https://app.toughtongueai.com - Developer Tools & API Playground: https://app.toughtongueai.com/developer - Documentation: https://app.toughtongueai.com/docs - API Best Practices: https://app.toughtongueai.com/docs/api-best-practices - Simple Integration Example: https://github.com/tough-tongue/ttai-simple-course --- # API Overview ## Base URL ``` https://api.toughtongueai.com/api/public ``` ## Authentication Every request requires a Bearer token: ``` Authorization: Bearer YOUR_API_TOKEN ``` Create tokens at: `https://app.toughtongueai.com/developer` ## Core Workflow ``` Create Scenario → Deploy (iframe/phone/bot) → Session created → Fetch results ↓ Webhook notifies your server ``` | Step | Action | Endpoint | | ---- | ------------------- | ------------------------------------- | | 1 | Create scenario | `POST /scenarios` | | 2a | Embed iframe | URL: `/embed/basic/SCENARIO_ID` | | 2b | Phone call | `POST /v2/sip/call` | | 2c | Meeting bot | `POST /v2/meeting-bots` | | 3 | Get session results | `GET /v2/sessions` | | 4 | Trigger analysis | `POST /v2/sessions/{id}/post-process` | | 5 | Get analytics | `GET /v2/analytics` | ## Conventions - All responses: JSON - Dates: ISO 8601 (`2025-02-26T18:02:00.945000`) - Pagination: `page` (1-based) + `limit` query params - IDs: 24-character hex strings (MongoDB ObjectId) - Errors: `{"detail": "error message"}` with appropriate HTTP status - Endpoints below show full paths (e.g. `POST /api/public/scenarios`) --- # MCP Server The public API is also exposed as a **Model Context Protocol (MCP) server** — a Streamable HTTP transport that lets agent clients (Cursor, Claude Code, Claude Desktop, etc.) call the platform as tools. Clients authenticate via OAuth 2.1 sign-in (no API key needed) or a PAT bearer header. Account tools delegate to authenticated HTTP handlers with the same auth, schemas, and behavior; the quickstart tool returns the MCP guide resource. V3 resource handlers are MCP-first and not customer REST reference endpoints yet. ## Endpoint ``` https://api.toughtongueai.com/api/public/mcp ``` - **Transport**: Streamable HTTP (official `mcp` Python SDK / FastMCP) — native `url` support in remote-capable clients; stdio-only clients bridge via `mcp-remote` - **Auth**: two options. (1) **OAuth 2.1 with dynamic client registration** — add the URL with no credentials and the client opens a browser sign-in; works in Cursor, Claude Code, Codex, Copilot, Windsurf, Gemini CLI, claude.ai web, ChatGPT web, and mcp-remote. The issued access token is a PAT under the hood. (2) **PAT bearer header** — `Authorization: Bearer YOUR_API_TOKEN` on every request; use for servers, CI, headless agents - **Rate limit**: 30 tool calls/min per token - **Tool count**: 35 curated tools - **SAFE-MCP**: `https://api.toughtongueai.com/api/public/mcp/directory` exposes 33 tools without autonomous outbound SIP dialing. It is endpoint-selected for future reviewed connector directories; plugins use the full endpoint. ## Client Configuration One-command install (skills + MCP into every detected coding agent; OAuth on first tool call): ```bash npx plugins add tough-tongue/toughtongue-skills ``` Or configure the server directly. OAuth (recommended — the client prompts a browser sign-in): ```json { "mcpServers": { "ttai": { "url": "https://api.toughtongueai.com/api/public/mcp" } } } ``` PAT bearer header: ```json { "mcpServers": { "ttai": { "url": "https://api.toughtongueai.com/api/public/mcp", "headers": { "Authorization": "Bearer YOUR_API_TOKEN" } } } } ``` ## Organization Scoping Account-scoped tools accept an optional `org_id` argument. Call `list_organizations` first, then pass the returned org `id` on subsequent tool calls. Omit `org_id` for personal context. (REST clients can use the `X-TT-ORG` header instead.) ## Tool Catalog V3 MCP tool names use the portable `v3_` prefix so OpenAI-backed clients can register them unchanged. | MCP tool | Underlying HTTP handler | Notes | | ----------------------------------- | ---------------------------------------------------- | --------------------------- | | `callme_before_using_tough_tongue_mcp` | Static `ttai://guide/mcp-agent` resource | Full quickstart as a tool result | | `list_organizations` | `GET /api/public/v2/organizations` | Call first for org IDs | | `v3_list_sessions` | `GET /api/public/v3/resources/sessions` | Compact page; opt-in exact count | | `list_sessions` | `GET /api/public/v2/sessions` | Legacy person/date/full-detail filters | | `get_sessions_batch` | `GET /api/public/v2/sessions/batch` | | | `get_analytics` | `GET /api/public/v2/analytics` | | | `create_session` | `POST /api/public/v2/session` | | | `post_process_session` | `POST /api/public/v2/sessions/{id}/post-process` | | | `list_meeting_bots` | `GET /api/public/v2/meeting-bots` | | | `schedule_meeting_bot` | `POST /api/public/v2/meeting-bots` | | | `delete_meeting_bot` | `DELETE /api/public/v2/meeting-bots/{id}` | destructive | | `list_sip_trunks` | `GET /api/public/v2/sip/trunks` | Pick `sip_trunk_id` to dial | | `list_sip_calls` | `GET /api/public/v2/sip/calls` | | | `create_sip_call` | `POST /api/public/v2/sip/call` | | | `create_sip_batch` | `POST /api/public/v2/sip/batch` | | | `delete_sip_call` | `DELETE /api/public/v2/sip/calls/{id}` | destructive | | `authenticate_browser` | `POST /api/public/v2/scenarios/browser/authenticate` | | | `get_balance` | `GET /api/public/balance` | | | `v3_list_scenarios` | `GET /api/public/v3/resources/scenarios` | Compact page; opt-in exact count | | `list_scenarios` | `GET /api/public/scenarios` | Legacy full list and free-text search | | `create_scenario` | `POST /api/public/scenarios` (no `id`) | Full authoring input/output | | `update_scenario` | `POST /api/public/scenarios` (`id` required) | Full authoring input/output | | `get_scenario` | `GET /api/public/scenarios/{id}` | Current or exact version-ID read | | `v3_get_scenario_version` | `GET /api/public/v3/resources/scenarios/{id}` | ID/name version selector | | `v3_list_scenario_versions` | `GET /api/public/v3/resources/scenarios/{id}/versions` | Complete, unpaginated list | | `get_session` | `GET /api/public/sessions/{id}` | Full session w/ transcript | | `list_collections` | `GET /api/public/collections` | | | `get_collection` | `GET /api/public/collections/{id}` | | | `create_scenario_access_token` | `POST /api/public/scenario-access-token` | | | `create_self_scenario_access_token` | `POST /api/public/scenario-access-token/me` | | | `v3_get_entitlements` | `GET /api/public/v3/entitlements` | Effective capability limits | | `v3_list_resource_types` | `GET /api/public/v3/resources` | Self-describing safe catalog | | `v3_list_resources` | `GET /api/public/v3/resources/{type}` | Bounded projected list | | `v3_get_resource` | `GET /api/public/v3/resources/{type}/{id}` | Safe projected detail | At the first Tough Tongue MCP action in a new conversation, call `callme_before_using_tough_tongue_mcp` unless `ttai://guide/mcp-agent` is already in context. It returns the complete quickstart and V3-first policy. **V3 replaces older reads by default:** use `v3_list_scenarios` instead of `list_scenarios` for current inventory and counts; use `v3_get_scenario_version` instead of `get_scenario` for known-ID current or version detail; and use `v3_list_sessions` instead of `list_sessions` for current inventory, exact Scenario/status filters, and counts. For an exact Scenario or Session count, call the matching V3 list with `limit: 1` and `include_total: true`; never fetch a full page solely to count. Use legacy `list_scenarios` only for free-text Scenario search, then resume V3 with the returned ID. Use legacy `list_sessions` only for person, date, metadata, learning, or enriched-result filters; `get_session` and `get_sessions_batch` remain detailed transcript reads. `get_balance`, `get_analytics`, call, and bot tools have no published V3 replacement. `v3_list_resource_types` returns its next-tool names, common-record semantics, list/detail behavior, group meanings, and the bounded generic directory types: `custom-functions`, `knowledge-bases`, `sip-trunks`, and `user-preferences`. --- # API: Scenario Management A **scenario** defines the AI agent's behavior. Create one via API, then deploy it through any channel (iframe, phone, meeting bot). Every user interaction creates a **session**. ## Create Scenario **`POST /api/public/scenarios`** `name` and `ai_instructions` are required. Every caller-controlled Scenario field is supported: content, linked Knowledge Bases and Custom Functions, agent/tool configuration, access controls, and coding Scenario settings. Server-owned identity, organization, version, audit, platform-curation, and retired commerce fields are rejected. Include an existing `id` to update only the fields you send. ```bash curl -X POST https://api.toughtongueai.com/api/public/scenarios \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "name": "Sales Objection Handling", "ai_instructions": "You are a skeptical enterprise buyer. The rep is selling {{ product_name }} to {{ company_name }}. Push back on pricing and demand ROI numbers.", "rubrik": "Rate on: objection handling (40%), value articulation (30%), closing technique (30%)", "is_public": false, "is_recording": true, "appearance": { "voice": "Aoede", "language_code": "en-US" }, "session_analysis": { "is_auto_analysis": true, "enable_extraction": true, "extraction_vars": [ {"name": "objections_raised", "description": "List of objections the prospect raised", "type": "list"}, {"name": "deal_closed", "description": "Did the rep successfully close", "type": "boolean"}, {"name": "confidence_score", "description": "Rep confidence level 1-10", "type": "number"} ] }, "tools_config": { "tools": { "card": {"should_register": true, "add_to_system_prompt": false, "tool_settings": null}, "end_session": {"should_register": true, "add_to_system_prompt": true, "tool_settings": {"disconnectDelaySeconds": 5}} } }, "user_metadata": {"department": "sales", "difficulty": "intermediate"} }' ``` **Response** (200): ```json { "id": "689e283d8ff2b6aca6239d8b", "name": "Sales Objection Handling", "type": "default", "description": null, "user_friendly_description": null, "ai_instructions": "You are a skeptical enterprise buyer.", "user_instructions": null, "rubrik": "Rate on: objection handling (40%), value articulation (30%), closing technique (30%)", "is_public": false, "is_recording": true, "recording_mode": "standard", "passcode": null, "analysis_access": null, "appearance": { "voice": "Aoede", "language_code": "en-US" }, "knowledge_base_ids": [], "custom_function_ids": [], "created_at": "2024-06-10T09:15:00Z", "updated_at": "2024-06-10T09:15:00Z" } ``` ## Update Scenario **`POST /api/public/scenarios`** (include `id` field) Every caller-controlled create field can be updated. Omitted fields remain unchanged; an explicit `null` clears a nullable field. ```bash curl -X POST https://api.toughtongueai.com/api/public/scenarios \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{"id": "689e283d8ff2b6aca6239d8b", "name": "Advanced Sales Training"}' ``` ## List Scenarios **`GET /api/public/scenarios`** Returns current Scenario heads you own or can access through a grant in the selected scope. Sensitive fields (ai_instructions, tools_config, memory) are excluded — use Get by ID for full details. ## Get Scenario **`GET /api/public/scenarios/{id}`** Returns the current Scenario configuration. Pass an exact archived `version_id` to select one immutable archive. ## Generate Scenario (AI) **`POST /api/public/v2/scenario/generate-advanced`** Generate scenario content (AI instructions, name, user-friendly description) from a short name or a context document. Returns generated text — it does not persist a scenario. Feed the result into Create Scenario. ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/scenario/generate-advanced \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{"name": "Cold call for a SaaS product", "context": "We sell CRM software to SMBs"}' ``` **Response:** ```json { "success": true, "instructions": "You are a busy SMB owner receiving a cold call...", "user_instructions": "Practice your cold-call opener...", "user_friendly_description": "Cold call practice for SaaS sales", "ai_name": "SMB Owner" } ``` ## Full Scenario Resource ```json { "id": "689e283d8ff2b6aca6239d8b", "name": "Customer Support Training", "description": "Internal team description", "ai_instructions": "You are a frustrated customer with a billing issue on {{ product_name }}.", "user_friendly_description": "Improve your customer service skills", "user_instructions": "Respond as you would in a live support conversation.", "rubrik": "Rate based on empathy, problem-solving, clarity", "knowledge_base_ids": ["KNOWLEDGE_BASE_ID"], "custom_function_ids": ["CUSTOM_FUNCTION_ID"], "pre_connect": { "pre_connect_function_id": "CUSTOM_FUNCTION_ID" }, "is_public": true, "passcode": null, "is_recording": true, "analysis_access": "default", "appearance": { "voice": "Aoede", "avatar_url": null, "language_code": "en-US" }, "memory": { "is_memory": true }, "session_analysis": { "is_auto_analysis": true, "is_auto_submit": false, "admin_email": "admin@example.com", "email_analysis": true, "email_transcript": false, "multimodal_analysis": false, "evaluation_target": null, "enable_extraction": true, "extraction_vars": [ { "name": "sentiment", "description": "Overall customer sentiment", "type": "text" } ] }, "ai_model_config": { "provider": "Ocean", "model": "medium-stable" }, "tools_config": { "tools": { "timer": { "should_register": true, "add_to_system_prompt": false, "tool_settings": null }, "end_session": { "should_register": true, "add_to_system_prompt": true, "tool_settings": { "disconnectDelaySeconds": 5 } } } }, "user_metadata": { "department": "sales", "priority": 5 }, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T14:20:00Z" } ``` --- ## Field Reference ### Core | Field | Type | Description | | --------------------------- | ------ | ----------------------------------------------------- | | `id` | string | Omit to create, include to update | | `name` | string | Display name (required) | | `description` | string | Internal admin-only description | | `ai_instructions` | string | System prompt. Supports `{{ variable }}` placeholders | | `user_friendly_description` | string | Shown to end users | | `rubrik` | string | Evaluation criteria for session analysis | ### Access & Recording | Field | Type | Description | | ----------------- | ------- | -------------------------------------------------------------------- | | `is_public` | boolean | Publicly accessible (default: true) | | `passcode` | string | Optional access code | | `is_recording` | boolean | Record session media on supported surfaces; transcription is configured separately | | `analysis_access` | string | `"default"` / `"always"` / `"never"` — where analysis shows | | `user_metadata` | object | Custom key-value pairs. Filterable via `meta_*` query params on List | ### Appearance | Field | Values | | -------------------------- | --------------------------------------------------------- | | `appearance.voice` | Aoede, Charon, Fenrir, Kore, Puck | | `appearance.avatar_url` | Custom avatar image URL | | `appearance.language_code` | BCP-47: "en-US", "fr-FR", "es-ES", "ja-JP", "de-DE", etc. | ### AI Model | Field | Values | | -------------------------- | ------------------------------------------------------------------------------- | | `ai_model_config.provider` | "Ocean", "Galaxy", "Landmass" | | `ai_model_config.model` | "medium", "medium-super", "medium-stable", "medium-nc", "medium-stable-nc"; Landmass: "gemini", "gemini-stable", "openai", "cascade" | | `memory.is_memory` | Agent remembers past sessions with same user | ### Session Analysis | Field | Description | | -------------------------------------- | ----------------------------------- | | `session_analysis.is_auto_analysis` | ON/OFF gate for analysis. If true, auto-runs | | `session_analysis.admin_email` | Comma-separated report recipients | | `session_analysis.is_auto_submit` | Skip user confirmation | | `session_analysis.email_analysis` | Email evaluation to admin | | `session_analysis.email_transcript` | Email transcript to admin | | `session_analysis.multimodal_analysis` | Include audio/video in evaluation | | `session_analysis.evaluation_target` | Who to evaluate (multi-participant) | | `session_analysis.enable_extraction` | Extract variables on session end | | `session_analysis.extraction_vars` | `[{name, description, type}]` | Extraction types: `text`, `number`, `boolean`, `list`, `date`. ### Dynamic Variables Place `{{ variable_name }}` in `ai_instructions`. Values injected at runtime: | Channel | How to pass | Example | | ----------- | ------------------------------------------ | ------------------------------------- | | Iframe | `t_` prefix query params | `?t_company_name=Acme&t_role=Manager` | | Phone call | `dynamic_vars` field in request body | `{"company_name": "Acme"}` | | API session | Not yet supported — use fixed instructions | — | ### Tools Config ```json "tools_config": { "tools": { "": { "should_register": true, "add_to_system_prompt": false, "tool_settings": null } } } ``` **Available tools:** timer, whiteboard, card, mcq, mermaid, pdf_upload, knowledge_base_search, end_session, google_slides, image_generation, notepad, code, slide_generation, browser, custom_function, emoji_reaction The legacy `arcade_gmail` and `arcade_calendar` keys do not register in-session tools. Use a custom function or a compatible MCP integration for email/calendar actions. Calendar-based meeting-bot scheduling is separate. **Tool-specific settings:** | Tool | `tool_settings` | | --------------- | ------------------------------------------------------- | | `end_session` | `{"disconnectDelaySeconds": 5}` | | `google_slides` | `{"embedUrl": "https://docs.google.com/.../pub?..."}` | | `browser` | `{"initialUrl": "https://...", "contextId": "ctx_..."}` | | All others | `null` | The `code` tool renders only when enabled and both top-level `coding_question` and `coding_solution` are populated. ### MCP Integrations Attach outbound MCP servers the agent can call during Ocean and Landmass sessions. | Field | Type | Description | | --------------------- | ----- | ---------------------------------------------------------------------------------------------------- | | `mcp_server_ids` | array | `catalog:` (curated) or `custom:` (org-public resource). Example: `["catalog:github","custom:67a1…"]` | ```json "mcp_server_ids": ["catalog:deepwiki", "custom:67a1aaaaaaaaaaaaaaaaaa"] ``` Create org-public MCP servers via `POST /api/integrations/mcp/servers` (`auth_headers` is `Header: Liquid value`, e.g. `Authorization: Bearer {{ NOTION_API_KEY }}`, max 3). Then attach with `custom:`. OAuth is not supported yet. ### Content commerce The public API does not expose or accept content commerce data. Scenario create and update requests that include the retired `pricing` field are rejected. --- # API: Session Management Every conversation creates a **session**. Use `GET /api/public/v2/sessions` to list enriched results and `GET /api/public/sessions/{session_id}` for session details. ## List Sessions (Enriched) **`GET /api/public/v2/sessions`** Primary session listing. Returns evaluation scores, report cards, extraction results, and analytics URLs. | Parameter | Required | Description | | ------------- | -------- | ---------------------------------------------- | | `scenario_id` | — | Filter by scenario | | `user_email` | — | Filter by email (comma-separated for multiple) | | `from_date` | — | Updated on/after (date or ISO 8601 timestamp) | | `to_date` | — | Updated on/before (date-only = whole UTC day) | **Additional filters:** `$gte_updated_at`, `$lte_updated_at`, `$gte_created_at`, `$lt_created_at`, `meta_*` (user_metadata filters). ```bash curl 'https://api.toughtongueai.com/api/public/v2/sessions?scenario_id=SCENARIO_ID&user_email=john@example.com' \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response:** ```json { "sessions": [ { "id": "67bf57183f44cdf6055a8166", "scenario_id": "67bf56c23f44cdf6055a8165", "scenario_name": "Customer Support Training", "created_at": "2025-02-26T18:02:00.945000", "completed_at": "2025-02-26T18:02:08.301000", "status": "completed", "user_name": "John Doe", "user_email": "john.doe@example.com", "duration": 450.5, "evaluation_results": { "final_score": 7.4, "overall_score": "7.4/10. Strong communication; could improve follow-up questions.", "report_card": [ { "topic": "Communication", "score": 8.0, "score_str": "8.0/10", "note": "Clear.", "weight": 40 } ] }, "analytics_url": "https://app.toughtongueai.com/analysis/67bf57183f44cdf6055a8166" } ], "page_meta": { "page": 1, "limit": 20, "total": 1 } } ``` ## Create Session **`POST /api/public/v2/session`** Upload an external transcript or audio recording to create a completed session for analysis. ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/session \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "scenario_id": "SCENARIO_ID", "transcript": "Agent: Hello, how can I help?\nUser: I have an issue...", "user_name": "John Doe", "user_email": "john@example.com", "force_analysis": true }' ``` | Field | Type | Required | Description | | --------------- | ------- | -------- | ----------------------------------------------------------------------- | | `scenario_id` | string | ✅ | Scenario to associate the session with | | `transcript` | string | — | Raw transcript text. Provide either `transcript` or `recording_url` | | `recording_url` | string | — | URL to audio/video recording. Transcribed unless scenario is multimodal | | `user_name` | string | — | Session participant name | | `user_email` | string | — | Session participant email | | `metadata` | object | — | Arbitrary key-value metadata attached to the session | | `dynamic_vars` | object | — | Session-level string vars (caller context, not shown in transcript) | | `force_analysis`| boolean | — | Run evaluation immediately after creation | **Response:** ```json { "session_id": "67bf57183f44cdf6055a8166", "analytics_url": "https://app.toughtongueai.com/analysis/67bf57183f44cdf6055a8166", "created_at": "2025-02-26T18:02:00.945000" } ``` **Notes:** - Provide exactly one of `transcript` or `recording_url` — not both. - Billing: audio recordings billed by duration; transcript uploads billed at a flat 1-minute rate. ## Batch Fetch Sessions **`GET /api/public/v2/sessions/batch`** Fetch multiple enriched sessions by ID in a single request — faster than filtering via List Sessions. Pass `session_ids` as repeated query params (max 500 per request). ```bash curl 'https://api.toughtongueai.com/api/public/v2/sessions/batch?session_ids=ID1&session_ids=ID2' \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` Response format matches List Sessions (Enriched). ## Get Session Details **`GET /api/public/sessions/{session_id}`** Full session data including transcript, evaluation, and extracted variables. > This route is the public transcript source; `GET /v2/sessions/batch` omits it. ```json { "id": "67c551e8aa4a6a7575d33955", "scenario_id": "679c838ec41cfcb298fd9ace", "scenario_name": "Customer Support Training", "status": "completed", "created_at": "2025-03-03T06:53:28.035000", "completed_at": "2025-03-03T06:54:14.346000", "user_name": "John Doe", "user_email": "john.doe@example.com", "transcript_url": "https://...s3.amazonaws.com/sessions/transcript.txt", "evaluation_results": { "overall_score": "8/10", "final_score": 7.4, "strengths": "Excellent active listening", "weaknesses": "Could improve solution presentation", "detailed_feedback": "The participant demonstrated strong...", "report_card": [ { "topic": "Communication", "score": 7.5, "score_str": "7.5/10", "note": "Clear articulation", "weight": 30 }, { "topic": "Problem Solving", "score": 8.0, "score_str": "8/10", "note": "Creative solutions", "weight": 40 } ] }, "extraction_results": { "objections_raised": ["pricing too high", "competitor has feature X"], "deal_closed": false, "confidence_score": 6 }, "improvement_results": { "improvement_areas": "Solution presentation, closing technique", "action_items": "1. Present solutions step-by-step\n2. Use trial closes earlier", "resources": "- Objection Handling Guide: [link]" } } ``` ### Key fields: | Field | Description | | -------------------------------- | ----------------------------------------------------------- | | `evaluation_results.final_score` | Numeric score (0-10) | | `evaluation_results.report_card` | Per-topic breakdown with weights | | `extraction_results` | Object keyed by extraction var names (from scenario config) | | `transcript_url` | S3 URL to full transcript text | | `improvement_results` | Actionable feedback and resources | --- ## Delete Session (V2, REST only) **`DELETE /api/public/v2/sessions/{session_id}`** Delete a `completed`, `terminated`, or `archived` session, its post-session chat, recording, and session-derived memory facts. For multi-participant meeting bots configured to create one session per participant, deleting one participant session also deletes their shared meeting recording. An assimilated contribution from the session may still persist in the memory system. Requires `EDIT` or `OWNER` access to the associated scenario. This destructive endpoint is intentionally excluded from MCP tools. ```bash curl -X DELETE https://api.toughtongueai.com/api/public/v2/sessions/SESSION_ID \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` Returns `{"ok": true}`. Add `X-TT-ORG: ORG_ID` for organization sessions; omit it for personal sessions. Invalid IDs, non-terminal states, and active post-session processing return 400. Invalid PATs or organization memberships return 401. Missing, wrong-context, and unauthorized sessions return 404. Limit: 10 requests per minute per user; exhaustion returns 429 with `Retry-After: 60`. Cleanup failures return 500 and leave the session record available for retry. --- ## Trigger Analysis **`POST /api/public/sessions/analyze`** Run evaluation on a completed session that wasn't auto-analyzed. Synchronous — the response carries the evaluation and improvement results once analysis completes. Use Post-Process (V2) below when a background trigger is enough. ```bash curl -X POST https://api.toughtongueai.com/api/public/sessions/analyze \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{"session_id": "SESSION_ID"}' ``` ## Post-Process (V2) **`POST /api/public/v2/sessions/{session_id}/post-process`** Run analysis and variable extraction in the background, as configured on the scenario. Jobs that already have results are skipped. Returns `{"ok": true}` immediately. To wait for a run, read `post_session_status.start_ts` first, then poll session details until it changes and `post_session_status.state` is `idle` or `failed`. ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/sessions/SESSION_ID/post-process \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{}' ``` **Notes:** - `run_analysis` and `run_extraction` are accepted but ignored; the scenario's settings decide what runs - Jobs that already have results are skipped, and so is a request made while analysis is running; a skipped request leaves the status unchanged - Extraction requires `extraction_vars` configured on the scenario - Async counterpart to `POST /sessions/analyze`, which returns results inline --- # API: Scenario Access Tokens ## Scenario Access Tokens **Endpoint**: `POST /api/public/scenario-access-token` Generate a private Scenario embed token. It defaults to one hour; set `valid_for_hours` from 1 through 168 for a different lifetime. Omit `email` for an anonymous runner. In an organization, `email` creates or reuses a dormant virtual user and must end in `@ORG_ID.toughtongueai.com`; in personal scope, it is available only to resellers and creates a regular user. To issue a self-billed token for the PAT holder, call `POST /api/public/scenario-access-token/me` instead; it does not accept `email`. ```bash curl -X POST https://api.toughtongueai.com/api/public/scenario-access-token \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{"scenario_id": "SCENARIO_ID", "valid_for_hours": 24}' ``` **Response:** ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_at": "2025-02-26T19:02:00.000Z", "iframe_src": "https://app.toughtongueai.com/embed/SCENARIO_ID?scenarioAccessToken=..." } ``` # API: Account ## Wallet Balance **Endpoint**: `GET /api/public/balance` Without `X-TT-ORG`, returns the PAT holder's personal wallet. With an organization header, every active organization member can read the shared balance; a quota-enabled organization returns the caller's member quota when allocated. | Header | Required | Description | | --- | --- | --- | | `Authorization` | Yes | `Bearer YOUR_API_TOKEN` | | `X-TT-ORG` | No | Organization ID from `list_organizations`; omit for personal scope | The response has a `scope` discriminator: - Personal: `scope: "personal"` and `balance`. - Organization: `scope: "organization"`, `quota_enabled`, and `org_balance`; `quota_balance` is populated when the caller has a quota wallet and `null` when quotas are disabled or no member quota has been allocated. This replaces the former top-level `available_minutes` and `last_updated` fields. ```bash curl -X GET https://api.toughtongueai.com/api/public/balance \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "X-TT-ORG: ORGANIZATION_ID" ``` **Response:** ```json { "scope": "organization", "quota_enabled": true, "org_balance": { "available_minutes": 4109.7, "last_updated": "2026-09-16T08:00:00Z" }, "quota_balance": { "available_minutes": 120, "last_updated": "2026-09-16T08:00:00Z" } } ``` --- # API: Meeting Bots ## Schedule Meeting Bot **Endpoint**: `POST /api/public/v2/meeting-bots` Schedule AI bots to join and record online meetings. ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/meeting-bots \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "scenario_id": "SCENARIO_ID", "meeting_url": "https://meet.google.com/abc-defg-hij", "meeting_provider": "google-meet", "scheduled_ts": "2025-01-15T10:30:00Z", "bot_name": "AI Assistant" }' ``` **Response:** ```json { "success": true, "bots": [{ "bot_id": "67bf57183f44cdf6055a8166", "session_id": "67bf57183f44cdf6055a8167" }] } ``` **Features:** - **Multi-URL Support**: Separate URLs with newlines to schedule multiple bots - **Meeting Providers**: `google-meet`, `zoom`, `teams` - **Flexible Scheduling**: Set `scheduled_ts` to `null` for immediate join --- ## List Meeting Bots **Endpoint**: `GET /api/public/v2/meeting-bots` **Filters:** `status`, `from_date`, `to_date`, `scenario_id`, `page`, `limit` ```bash curl -X GET "https://api.toughtongueai.com/api/public/v2/meeting-bots?status=call_ended&from_date=2024-01-01" \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` --- ## Delete Meeting Bot **Endpoint**: `DELETE /api/public/v2/meeting-bots/{bot_id}` Only bots with status `pending` or `scheduled` can be deleted. ```bash curl -X DELETE https://api.toughtongueai.com/api/public/v2/meeting-bots/BOT_ID \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` --- # API: Phone Calls (SIP) Deploy any scenario over the phone. The AI places (or receives) a call, runs the conversation, then generates a session with full transcript and analysis. ## List SIP Trunks **`GET /api/public/v2/sip/trunks`** List configured SIP trunks for the caller (org-scoped when `X-TT-ORG` is set). Use a returned `sip_trunk_id` when initiating calls. ```bash curl https://api.toughtongueai.com/api/public/v2/sip/trunks \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` ## Initiate Phone Call **`POST /api/public/v2/sip/call`** ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/sip/call \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "scenario_id": "SCENARIO_ID", "sip_trunk_id": "SIP_TRUNK_ID", "phone_number": "+14155551234", "user_name": "John Doe", "dynamic_vars": { "patient_name": "John Doe", "appointment_type": "dental checkup", "date": "January 15, 2025" } }' ``` **Response:** ```json { "success": true, "call_id": "67bf57183f44cdf6055a8166", "session_id": null, "message": "Call initiated" } ``` ### Parameters | Field | Type | Required | Description | | -------------- | ------ | -------- | --------------------------------------------------------------- | | `scenario_id` | string | ✅ | Which AI agent to use | | `sip_trunk_id` | string | ✅ | Your SIP trunk (from Settings → SIP Trunks) | | `phone_number` | string | ✅ | E.164 format: `+14155551234` | | `user_name` | string | — | Recipient name (shows in session) | | `user_email` | string | — | Recipient email | | `scheduled_ts` | string | — | ISO 8601 datetime. Omit = call immediately | | `dynamic_vars` | object | — | Key-value pairs substituted into `{{ var }}` in ai_instructions | ### Dynamic Variables If your scenario's `ai_instructions` contains `{{ variable_name }}` placeholders, pass values via `dynamic_vars`: **Scenario prompt:** ``` You are calling {{ patient_name }} to confirm their {{ appointment_type }} on {{ date }}. Be warm and professional. If they need to reschedule, offer the next 3 available slots. ``` **API call:** ```json { "dynamic_vars": { "patient_name": "Jane Smith", "appointment_type": "dental checkup", "date": "January 15, 2025" } } ``` Result: The AI speaks with all placeholders replaced by actual values. --- ## Batch Phone Calls **`POST /api/public/v2/sip/batch`** Dispatch the same scenario to multiple phone numbers. Each entry can have its own `dynamic_vars`. ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/sip/batch \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -d '{ "scenario_id": "SCENARIO_ID", "sip_trunk_id": "SIP_TRUNK_ID", "entries": [ { "phone_number": "+14155551234", "user_name": "John Doe", "dynamic_vars": {"patient_name": "John Doe", "date": "Jan 15"} }, { "phone_number": "+14155555678", "user_name": "Jane Smith", "dynamic_vars": {"patient_name": "Jane Smith", "date": "Jan 16"} } ] }' ``` **Response:** ```json { "success": true, "batch_id": "67bf57183f44cdf6055a8168", "total_calls": 2, "message": "Created 2 call records, batch processing scheduled" } ``` **Batch behavior:** - Processes every 5 minutes - Concurrency limited by trunk's `max_concurrency` setting - Track progress: `GET /api/public/v2/sip/calls?batch_id=BATCH_ID` --- ## List Phone Calls **`GET /api/public/v2/sip/calls`** | Filter | Values | | ----------------------- | --------------------------------------------------------- | | `status` | pending, scheduled, in_call_recording, call_ended, failed | | `call_type` | `sip_call` (outbound), `sip_inbound` (comma-separated) | | `scenario_id` | Filter by scenario | | `batch_id` | Filter by batch | | `from_date` / `to_date` | Date range | | `page` / `limit` | Pagination | --- ## Delete Phone Call **`DELETE /api/public/v2/sip/calls/{call_id}`** Only calls with status `pending` or `scheduled` can be deleted. --- # API: Analytics ## Unified Analytics **Endpoint**: `GET /api/public/v2/analytics` Get comprehensive analytics data in a single API call. ```bash curl -X GET "https://api.toughtongueai.com/api/public/v2/analytics?is_org=false&start_date=2024-01-01&end_date=2024-12-31" \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` **Response:** ```json { "scenarios": [ { "id": "67bf56c23f44cdf6055a8165", "name": "Customer Support Training", "created_at": "2025-02-26T18:01:38.734000", "is_public": true, "session_count": 15, "total_duration_seconds": 6750.5 } ], "org_dashboard": { "summary": { "total_sessions": 25, "completed_sessions": 23, "total_minutes": 125.5, "wallet_balance": 874.5 }, "usage_by_member": [{ "user_id": "user_123", "total_minutes": 45.2, "session_count": 8 }], "time_series": [{ "date": "2024-01-15", "session_count": 5, "total_minutes": 42.3 }] }, "member_usage": { "members": [ { "user_id": "user_123", "name": "John Doe", "email": "john@example.com", "session_count": 8, "total_minutes": 45.2 } ] } } ``` **Parameters:** - `is_org` — filter for org-level or personal data - `start_date` / `end_date` — date range (YYYY-MM-DD) --- # Embedding Scenarios (Iframes) Embed any scenario in your website with a single ` ``` --- ## Private Scenario Access For non-public scenarios, generate a short-lived access token on your server. Replace `IFRAME_SRC_FROM_TOKEN_RESPONSE` below with the complete returned `iframe_src` URL. It already includes the correct host (including the subdomain for organization scenarios) and the `scenarioAccessToken` parameter: ```html ``` - Use `scenarioAccessToken` (not `accessToken`) - Tokens default to 1 hour; `valid_for_hours` accepts 1–168 hours. Generate server-side and use the returned `iframe_src` and `expires_at` - See API: Scenario Access Tokens for the token generation endpoint --- ## User Identification Skip the name/email dialog by passing user info: ``` ?userName=John%20Doe&userEmail=john@example.com ``` --- ## Customization Parameters | Parameter | Description | Example | | ---------------- | ------------------------- | ---------------------------- | | `name` | Custom conversation title | `?name=Support%20Training` | | `color` | Basic only: circle color (violet, indigo, blue, cyan, teal; random when omitted) | `?color=violet` | | `bg` | All styles: background color; theme background when omitted; supports transparent | `?bg=black` | | `pulse` | Minimal only: pulse bars; off by default | `?pulse=true` | | `hidePoweredBy` | Hide ToughTongue branding | `?hidePoweredBy=true` | | `maxDuration` | Auto-end after N seconds | `?maxDuration=600` | | `voice` | Override AI voice | `?voice=Aoede` | | `accent` | Override TTS accent (BCP-47 language code) | `?accent=en-GB` | | `avatarUrl` | Custom avatar image | `?avatarUrl=https://...` | | `avatarId` | Default avatar (1-5) | `?avatarId=3` | | `restoreSession` | Resume session | `?restoreSession=SESSION_ID` | --- ## Transcription, Recording, and Analysis - Full embeds transcribe by default; `transcribe=false` disables transcription. - Basic and Minimal require `transcribe=true`. Scenario-level disabled transcription overrides the URL. - Full embeds follow the scenario recording setting; `disableRecord=true` disables recording. Basic and Minimal always disable recording. - Automatic analysis follows the scenario's Analysis settings. `disableEval=true` suppresses the embed's automatic evaluation request. - Iframes never redirect to analysis. With transcription enabled, scenario `analysis_access=always` offers an external link; `default` and `never` do not. Analysis Access does not enable transcription or recording. ```html ``` --- ## Dynamic Variables Substitute `{{ variable_name }}` placeholders in `ai_instructions` at runtime using `t_` prefixed params: ```html ``` **Rules:** - Prefix every variable with `t_` in the URL - Variable name after `t_` must match what's in `ai_instructions` - URL-encode values (spaces → `%20`) - Works with all embed styles (full, basic, minimal) **Example scenario prompt:** ``` You are interviewing {{ candidate_name }} for the {{ role }} position at {{ company_name }}. Focus on their experience with {{ tech_stack }}. ``` **Embed URL:** `?t_candidate_name=Jane&t_role=SRE&t_company_name=Acme&t_tech_stack=Kubernetes` --- ## Browser Tool Authentication If your scenario uses the `browser` tool, pre-authenticate so users don't have to log in during conversations. Requires EDIT+ access to the scenario. ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/scenarios/browser/authenticate \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"scenario_id": "SCENARIO_ID", "initial_url": "https://app.example.com/login", "enable_recording_tool": false}' ``` Returns `{embed_url, expires_at}` — no browser session is created yet. Opening `embed_url` starts the live browser session. Use **Save & close session** to persist its authenticated context for future conversations on that scenario. Closing the tab or removing the iframe does not save authentication. `embed_url` defaults to a 20-minute lifetime; use `expires_at` and mint a fresh link when needed. Pass `enable_recording_tool: true` to also show a Workflows tab for recording a replayable demo. --- ## Iframe Events Embedded scenarios emit events via `window.postMessage`: | Event | When fired | | -------------- | ---------------------------------------- | | `onStart` | Session begins (agent connected) | | `onStop` | Session ends (agent disconnected) | | `onTerminated` | Session force-terminated (timeout/error) | | `onSubmit` | Post-session submission completed | `onStop` does not guarantee that session data has finished saving. `onSubmit` waits for transcript/session persistence and any required recording upload, but does not wait for analysis to finish. Use the `session.analyzed` webhook or session processing status to determine when scores are ready. **Payload:** ```json { "event": "onStop", "sessionId": "abc123", "timestamp": 1705312800000 } ``` **Listener:** ```javascript const iframe = document.querySelector('iframe'); window.addEventListener("message", (event) => { if (event.source !== iframe?.contentWindow) return; if (event.origin !== "https://app.toughtongueai.com") return; const { event: name, sessionId } = event.data || {}; if (name === "onSubmit" && sessionId) { // Session saved — the results page waits for analysis before displaying scores. window.location.href = `/results/${sessionId}`; } }); ``` --- ## Sending Data to Iframe Inject evaluator notes into the running session: ```javascript const iframe = document.querySelector('iframe'); iframe.contentWindow.postMessage( { type: "session_notes", notes: [ { text: "Strong opening, needs work on closing", timestamp: Date.now(), source: "evaluator", }, ], }, "https://app.toughtongueai.com", ); ``` --- # Integrations ## Meeting Bots AI agents join meetings and produce session data according to the scenario's transcription, recording, and analysis settings. **API**: `POST /api/public/v2/meeting-bots` (see Meeting Bots API section) **UI Setup:** 1. Create scenario 2. Library → Scenario → Meeting Bot 3. Choose mode: - **Custom**: Provide meeting URL + scheduled time - **Calendar**: Grant access, optionally filter by keyword (e.g., "interview") **Supported platforms**: Google Meet and Zoom in the app's deployment picker; Google Meet, Zoom, and Microsoft Teams through the Meeting Bots API. Supply an existing meeting URL when dispatching from the app. Access depends on the account or organization's Meeting Bot entitlement and available balance. **Limitations**: Best in quiet rooms with one speaker at a time. --- ## Google Slides AI navigates an existing Google Slides deck during conversation. Enable: `tools_config.tools.google_slides`. Share the deck publicly or with the platform service account. Agent can advance slides, jump to specific slides, and describe content. --- ## Phone Integration (SIP) Voice agents make real phone calls via your SIP trunk. **API**: `POST /api/public/v2/sip/call` and `POST /api/public/v2/sip/batch` (see Phone Calls API section) **Setup:** 1. Create SIP Trunk in your telephony provider (Twilio, Plivo, Exotel, Telnyx, Vonage) 2. Get: Termination URI, SIP Username, SIP Password 3. Assign phone number to trunk 4. In Tough Tongue AI: Scenario → Phone Call → SIP Configuration → Save **Calling:** - Single: API or UI - Batch: API with `entries[]` array, or CSV upload in UI - Scheduled: set `scheduled_ts` field - Dynamic context: `dynamic_vars` substitutes `{{ var }}` in prompt --- ## Outbound Webhooks Real-time HTTP notifications when session lifecycle events occur. ### Events | Event | When | Typical action | | -------------------- | ------------------- | -------------------------- | | `session.started` | Session created | Log start, update CRM | | `session.completed` | Session ended | Fetch transcript | | `session.analyzed` | Results persisted | Display results in your UI | | `session.extracted` | Variables persisted | Push to database | | `session.terminated` | Force-terminated | Log failure | | `post-session.done` | All work persisted | Trigger final sync | **Scope**: Organisation-only. Personal sessions don't fire webhooks. ### Setup 1. Create POST endpoint (must respond 2xx within 10s) 2. Developer → Webhooks → Add → select events → Create 3. Copy signing secret (shown once) ### Signature Verification Every request includes `webhook-id`, `webhook-timestamp`, `webhook-signature` headers. ```python # Python from standardwebhooks import Webhook wh = Webhook(signing_secret) wh.verify(request.body, dict(request.headers)) ``` ```javascript // Node.js import { Webhook } from "standardwebhooks"; const wh = new Webhook(signingSecret); wh.verify(rawBody, headers); ``` Reject if timestamp > 5 minutes old (replay protection). ### Payload ```json { "event": "session.analyzed", "id": "evt_abc123", "ts": "2026-05-13T10:00:00.000Z", "data": { "session_id": "664a1f...", "org_id": "663e2a...", "scenario_id": "6621bc..." } } ``` Most events include IDs only — fetch full details from `GET /v2/sessions/batch?session_ids={id}`. `post-session.done` also includes `post_session_state` (`idle` or `failed`) and `failed_reason` (`null` on success). Fetch guarantees: - After `session.analyzed`, the session returns `evaluation_results` and `improvement_results` when produced. - After `session.extracted`, the session returns `extraction_results`. - After `post-session.done`, the session returns all completed post-session outputs, including analysis, extraction, and scenario learning when configured. ### Delivery Behavior - No retries. Use `id` for idempotency. - 7-day delivery log in dashboard. - Heavy processing should be async (respond 200 immediately, process in background). --- # Scenario Configuration Guide ## Custom Functions (HTTP Tools) Extend agents with HTTP tools that call your APIs during conversation — same pattern as OpenAI function calling. 1. Define: name, description, HTTP method, URL, headers, JSON Schema for params 2. AI decides when to invoke during conversation 3. Platform calls your endpoint using the configured HTTP method and AI-generated arguments 4. Your server responds (text or JSON, max 15,000 chars) 5. AI speaks the result to the user **Use cases:** CRM lookup, send email, book appointment, update database, trigger workflow. In-call functions are exposed as `api_`. For work that needs a completed transcript or final analysis, configure a `post_session` function in the scenario's Analysis settings; an in-call tool description does not schedule a post-session hook. Requests default to a 120-second timeout; `timeout_ms` accepts 1,000–300,000 milliseconds. --- ## Available Agent Tools Enable per-scenario via `tools_config.tools..should_register = true`: | Tool | Key | What it does | | ---------------- | ----------------------- | ------------------------------------------------ | | Cards | `card` | Display info cards, frameworks, takeaways | | MCQ | `mcq` | Multiple-choice quiz questions | | Slide Generation | `slide_generation` | AI-generated slide decks with images | | Google Slides | `google_slides` | Navigate an existing slide deck | | Scenario Memory | Scenario setting | Carry a rolling summary into future sessions | | Knowledge Base | `knowledge_base_search` | RAG over uploaded documents | | Image Generation | `image_generation` | On-demand AI images | | Mermaid Diagrams | `mermaid` | Flowcharts, sequence diagrams | | Whiteboard | `whiteboard` | Interactive drawing canvas | | Browser | `browser` | Cloud browser (navigate, click, fill forms) | | Custom Function | `custom_function` | Call your HTTP endpoints (see above) | | Emoji Reaction | `emoji_reaction` | Floating emoji reactions | | PDF Upload | `pdf_upload` | Accept documents mid-session | | Timer | `timer` | Countdown for exercises | | Notepad | `notepad` | Shared notes during session | | Coding Editor | `code` | Shared coding interview editor and test runner | | End Session | `end_session` | Programmatic session termination | `arcade_gmail` and `arcade_calendar` are legacy configuration keys without in-session registration. Use a custom function or compatible MCP integration for those actions. **Configuration IDs and function names:** Use configuration IDs in `tools_config.tools` to enable tools. Use their function names in AI instructions so prompts match the callable tools. | Tool | Configuration ID | Function name for prompts | | --- | --- | --- | | Slide Generation | `slide_generation` | `generateSlide` | | Cards | `card` | `runCardCommand` | | MCQ | `mcq` | `runMCQCommand` | | Emoji Reactions | `emoji_reaction` | `showEmojiReaction` | | End Session | `end_session` | `endSession` | For example, enable `tools_config.tools.slide_generation.should_register = true`, then write "Use generateSlide to show the scenario context" in the prompt. Keep configuration IDs in API payloads. Exact function names remove ambiguity even when the AI could infer intent from a descriptive name. **Tool config structure:** ```json "tools_config": { "tools": { "browser": { "should_register": true, "add_to_system_prompt": true, "tool_settings": {"initialUrl": "https://app.example.com", "contextId": "ctx_abc123"} }, "end_session": { "should_register": true, "add_to_system_prompt": true, "tool_settings": {"disconnectDelaySeconds": 5} } } } ``` --- ## Browser Tool Authentication Pre-authenticate a cloud browser so users skip login during sessions. Requires EDIT+ access to the scenario: ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/scenarios/browser/authenticate \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"scenario_id": "SCENARIO_ID", "initial_url": "https://example.com/login"}' ``` Response returns `{embed_url, expires_at}` — no session is created yet. Open `embed_url`, sign in through the live browser, then use **Save & close session** to persist authentication for future sessions. Closing the tab or removing the iframe does not save authentication. The link defaults to a 20-minute lifetime; use the returned `expires_at`. --- ## Conversation Strategies | Strategy | Behavior | | ------------- | ------------------------------------------------------------ | | **Talkative** | AI leads conversation; continues even if user is silent | | **Quiz** | Session ends → immediate analysis; no avatar shown | | **Conductor** | Timed prompts at specific moments; auto-ends at set duration | --- ## Choosing an AI Model Set the model via `ai_model_config` (`provider` + `model`). Most scenarios never need to change it — pick based on your goal: | Goal | Use | | --------------------------------------- | ---------------------------- | | General purpose / phone & cold-call | Ocean (`medium-stable`) | | Emotional, human-like roleplay | Galaxy (`medium`) | | Multilingual conversations | Galaxy (`medium`) | | Cloned/specific voice or BYOK pipeline | Landmass (`cascade`) | - **Ocean** and **Galaxy** are native **voice-to-voice** models — they hear tone and emotion directly and respond with low latency. Ocean is the best all-round default (strong tool calling, reliable on phone); Galaxy is best for emotional expression and language range. - **Cascade** (Landmass) runs a **STT → LLM → TTS** pipeline. It trades some emotional read for full control: any voice (including cloned voices via Cartesia), your choice of LLM/TTS provider, and **bring-your-own-key (BYOK)**. See the [Choosing an AI Model](https://app.toughtongueai.com/docs/guides/choosing-ai-model) guide for details. --- ## Writing AI Instructions The `ai_instructions` field is the system prompt. Best practices: - **Role**: Start with "You are [Role]" — define persona, tone, expertise level - **Agenda**: List questions/topics in order; specify follow-up depth - **Dynamic content**: Use `{{ variable }}` placeholders (see Dynamic Variables) - **Boundaries**: State what the AI should NOT discuss or reveal - **Evaluation**: Pair with `rubrik` for post-session scoring criteria Example: ``` You are an HR interviewer conducting a behavioral interview for a {{ role_title }} position at {{ company_name }}. Ask 4 questions using the STAR method: 1. Leadership challenge 2. Conflict resolution 3. Technical decision under pressure 4. Cross-functional collaboration For each answer: probe for specifics, ask "what was the outcome?", challenge vague responses. Do NOT reveal the scoring rubric. End after all 4 questions with a brief summary. ``` --- ## Scenario Patterns ### Minimal (embed immediately): ```json { "name": "Sales Objection Handler", "ai_instructions": "You are a skeptical prospect. Push back on pricing.", "is_public": true } ``` ### Full-featured (analysis + extraction + tools): ```json { "name": "Customer Support QA", "ai_instructions": "You are a frustrated customer with a billing dispute on {{ product_name }}.", "is_recording": true, "session_analysis": { "is_auto_analysis": true, "enable_extraction": true, "extraction_vars": [ { "name": "resolution_offered", "description": "What solution was proposed", "type": "text" }, { "name": "customer_satisfied", "description": "Did customer express satisfaction", "type": "boolean" }, { "name": "escalation_needed", "description": "Was escalation requested", "type": "boolean" } ] }, "tools_config": { "tools": { "card": { "should_register": true, "add_to_system_prompt": false, "tool_settings": null }, "knowledge_base_search": { "should_register": true, "add_to_system_prompt": true, "tool_settings": null }, "end_session": { "should_register": true, "add_to_system_prompt": true, "tool_settings": { "disconnectDelaySeconds": 3 } } } } } ``` ### Phone agent with dynamic vars: ```json { "name": "Appointment Reminder", "ai_instructions": "Call {{ patient_name }} to confirm their {{ appointment_type }} on {{ date }}. Be warm and professional.", "is_recording": true, "session_analysis": { "is_auto_analysis": true, "enable_extraction": true, "extraction_vars": [ { "name": "confirmed", "description": "Did they confirm", "type": "boolean" }, { "name": "reschedule_date", "description": "New date if rescheduling", "type": "date" } ] } } ``` Then dispatch via phone: ```bash curl -X POST https://api.toughtongueai.com/api/public/v2/sip/call \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "scenario_id": "SCENARIO_ID", "phone_number": "+14155551234", "dynamic_vars": {"patient_name": "Jane Smith", "appointment_type": "dental checkup", "date": "Jan 15"} }' ``` --- # Integration Patterns & Session Management ## End-to-End Integration Pattern ``` Your Backend Tough Tongue AI ─────────── ─────────────── 1. POST /scenarios → Create scenario (returns scenario_id) 2. Render iframe with ID → User starts session 3. Listen postMessage onSubmit → Session data saved; get session_id 4. POST /v2/sessions/{id}/post-process → Trigger analysis + extraction 5. Webhook: session.analyzed ← Your server notified 6. GET /v2/sessions/batch → Fetch scores, transcript, extracted vars ``` ## Sample Repositories - **Simple Course** (iframe + lifecycle events + analysis): https://github.com/tough-tongue/ttai-simple-course - **AI Course Creation** (OpenAI generates scenarios): https://github.com/ajitesh123/tough-tongue-starter --- ## Choosing Iframe Type | Type | URL path | Transcription | Analysis | Best for | | ------- | ---------------------------- | ----------------------- | ------------------- | --------------------- | | Full | `/embed/SCENARIO_ID` | On unless disabled | When configured | Formal evaluations | | Basic | `/embed/basic/SCENARIO_ID` | `transcribe=true` | When transcribed and configured | Avatar conversations | | Minimal | `/embed/minimal/SCENARIO_ID` | `transcribe=true` | When transcribed and configured | Compact audio conversations | Enable transcription on Basic/Minimal with `transcribe=true` in the embed URL. Full embeds accept `transcribe=false` to disable it. The scenario's `strategy.disable_transcription` must also be false for transcription to run. `is_recording` controls media recording separately; Basic/Minimal always disable recording. `analysis_access` controls participant access, not transcript persistence: `never` hides analysis, `default` keeps the iframe's completion screen Close-only, and `always` exposes an external analysis link when transcription is enabled. Iframes do not redirect themselves to analysis. --- ## User Identity Mapping Pass user info on every iframe to track sessions per user: ``` /embed/basic/SCENARIO_ID?userName=John%20Doe&userEmail=john@company.com ``` Then filter sessions by email: `GET /v2/sessions?scenario_id=X&user_email=john@company.com` --- ## Session Lifecycle | State | Meaning | | ------------- | ------------------------------------ | | `active` | Session is active | | `completed` | Session ended normally | | `terminated` | Force-terminated (timeout, system) | | `archived` | Session has been archived | **Duration**: Set `strategy.max_duration_seconds` on the scenario or pass a positive `maxDuration` in seconds in the run/embed URL. The URL override takes precedence. Actual availability also depends on the selected model, usage balance, and connection. **Restore**: `?restoreSession=SESSION_ID` in iframe URL to resume interrupted sessions. --- ## Post-Session Data Pipeline After session completes: 1. **Analysis** (if `is_auto_analysis: true`): Produces `evaluation_results` with overall_score, strengths, weaknesses, report_card 2. **Extraction** (if `enable_extraction: true`): Produces `extraction_results` with your defined variables 3. **Webhook** fires `session.analyzed` / `session.extracted` to your server **Manual trigger**: `POST /v2/sessions/{id}/post-process` with an empty body (`{}`) --- ## Organizations & Multi-Tenant - Create org with custom subdomain and branding - Assign scenarios to org - Organization roles: Learner (practice; cannot create scenarios), Member (create scenarios), Admin (manage the team), Owner (organization creator) - Scenario access is separate: interact and view own sessions, view all sessions, or edit. Organization roles do not automatically grant access to private scenario content - CSV role values: `learner` or `view` = Learner, `read` = Member, `edit` = Admin; `owner` is not accepted - Select organization context for PAT-authenticated requests with `X-TT-ORG` - Webhooks are org-scoped ### List Organizations **`GET /api/public/v2/organizations`** Returns the organizations the authenticated token belongs to. Call this first to get org IDs for scoping. ```bash curl https://api.toughtongueai.com/api/public/v2/organizations \ -H "Authorization: Bearer YOUR_API_TOKEN" ``` ### Organization Scoping By default, requests act on your **personal** context. To act within an organization, send the `X-TT-ORG` header with the org ID: ```bash curl 'https://api.toughtongueai.com/api/public/v2/sessions' \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "X-TT-ORG: ORG_ID" ``` On the MCP server, pass `org_id` as a tool argument instead of the header. --- ## Language & Voice | Config | Where | Example | | ------------------ | -------------------------- | --------------------------------- | | Voice accent | `appearance.language_code` | `"fr-FR"`, `"es-ES"`, `"ja-JP"` | | Response language | `ai_instructions` | "Always respond in French" | | Voice selection | `appearance.voice` | Aoede, Charon, Fenrir, Kore, Puck | | Override voice per-embed | iframe `?voice=` | `?voice=Aoede` | | Override accent per-embed | iframe `?accent=` | `?accent=en-GB`, `?accent=fr-FR` | --- ## Secure Access Patterns **Private scenario + access token:** 1. `POST /api/public/scenario-access-token` → token defaults to 1 hour; `valid_for_hours` accepts 1–168. Include `X-TT-ORG` when issuing a token for an organization scenario 2. Use the returned `iframe_src` to preserve the organization subdomain and `scenarioAccessToken` parameter **Single-use links** (UI-generated): - Each link works once only - Choose 24-hour or 7-day expiry - Tracks session to your dashboard **Passcode protection:** - Set `passcode` on scenario - User must enter code before session starts --- ## Support - **Technical Support**: help@getarchieai.com - **30min Consultation**: https://cal.com/ajitesh/30min - **Community (Discord)**: https://discord.gg/NfTPT3HsSj ## Video Walkthroughs - **API Integration Guide**: https://www.loom.com/share/d9ae326da7b54f2f8d84120bd3399d0d - **Platform Demo**: https://www.loom.com/share/04a13f04a22b4547bf0dd9bd99a9db62 ## Documentation Links - Full docs: https://app.toughtongueai.com/docs - API reference: https://app.toughtongueai.com/docs/api-reference - API best practices: https://app.toughtongueai.com/docs/api-best-practices - Webhook docs: https://app.toughtongueai.com/docs/integrations/webhooks - Phone integration: https://app.toughtongueai.com/docs/integrations/phone-integration - Google Meet agent: https://app.toughtongueai.com/docs/integrations/google-meet-agent - Custom tools guide: https://app.toughtongueai.com/docs/guides/custom-tools - Tool reference: https://app.toughtongueai.com/docs/guides/tool-reference - Choosing an AI model: https://app.toughtongueai.com/docs/guides/choosing-ai-model - Release notes: https://app.toughtongueai.com/docs/resources/release-notes ## Sample Code - **iframe + events + analysis**: https://github.com/tough-tongue/ttai-simple-course - **AI-generated scenarios**: https://github.com/ajitesh123/tough-tongue-starter --- ## Known Limitations | Limitation | Detail | | ---------------- | ------------------------------------------------- | | Session duration | ~10–12 min per session | | Browser | Chrome recommended; Safari has recording issues | | Meeting bots | Google Meet only (Zoom/Teams coming) | | Meeting bots | Background noise sensitive; best with one speaker | | Webhooks | No retries; org-scoped only | | Phone calls | Outbound and inbound (`call_type`: sip_call / sip_inbound) |