### Quick Start with VAPI Voice Agent using cURL Source: https://github.com/attendee-labs/attendee/blob/main/docs/voice_agents.md This cURL command demonstrates how to set up a VAPI voice agent. Ensure you replace placeholders with your actual API token, assistant ID, and public key. The agent will join the meeting and start recording. ```bash curl -X POST https://app.attendee.dev/api/v1/bots \ -H 'Authorization: Token YOUR_API_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "meeting_url": "https://meet.google.com/abc-def-ghi", "bot_name": "Vapi Agent", "voice_agent_settings": { "url": "https://attendee.dev/vapi_voice_agent_example?assistant_id=YOUR_ASSISTANT_ID&public_key=YOUR_PUBLIC_KEY" }, }' ``` -------------------------------- ### Install pre-commit hooks Source: https://github.com/attendee-labs/attendee/blob/main/CONTRIBUTING.md Commands to install pre-commit hooks for automated linting and formatting. ```bash pip install pre-commit pre-commit install ``` -------------------------------- ### Start development services Source: https://github.com/attendee-labs/attendee/blob/main/README.md Launches all services defined in the development Docker compose file. ```bash docker compose -f dev.docker-compose.yaml up ``` -------------------------------- ### Set up the development environment Source: https://github.com/attendee-labs/attendee/blob/main/CONTRIBUTING.md Commands to build the Docker image, initialize environment variables, start services, and run database migrations. ```bash # Build the Docker image (takes ~5 minutes) docker compose -f dev.docker-compose.yaml build # Create local environment variables docker compose -f dev.docker-compose.yaml run --rm attendee-app-local python init_env.py > .env # Edit .env and add your AWS credentials # Start all services docker compose -f dev.docker-compose.yaml up # In a separate terminal, run migrations docker compose -f dev.docker-compose.yaml exec attendee-app-local python manage.py migrate ``` -------------------------------- ### Get Platform Label Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Returns a user-friendly label for a given platform identifier. ```javascript function getPlatformLabel(platform) { return platform === "google\_meet" ? "Google Meet" : "Teams"; } ``` -------------------------------- ### Example Request to Custom Transcription Service Source: https://github.com/attendee-labs/attendee/blob/main/docs/custom_async_transcription.md This cURL command demonstrates how Attendee sends audio segments and custom parameters to your self-hosted transcription endpoint. ```bash curl -X POST 'http://your-service.com/transcribe' \ -F 'audio=@audio.pcm' \ -F 'language=fr-FR' \ -F 'custom_param=value' ``` -------------------------------- ### Calendar Connection Failure Data Example Source: https://github.com/attendee-labs/attendee/blob/main/docs/webhooks.md Example of the 'connection_failure_data' field within the 'calendar.state_change' webhook payload, showing authentication error details. ```json { "error": "Google Authentication error: {'error': 'invalid_grant'}", "timestamp": "2023-07-15T14:30:45.123456Z" } ``` -------------------------------- ### GET /api/v1/app_sessions/{id}/media Source: https://github.com/attendee-labs/attendee/blob/main/docs/zoom_rtms.md Retrieve recording and media files for a completed app session. ```APIDOC ## GET /api/v1/app_sessions/{id}/media ### Description Returns the recording and media files for a completed app session. This endpoint is only available after the session has moved to the `ended` state. ### Method GET ### Endpoint /api/v1/app_sessions/{id}/media ### Parameters #### Path Parameters - **id** (string) - Required - The ID of the app session. ### Response #### Success Response (200) - **media_files** (array) - An array of objects, each containing information about a media file (e.g., URL, type, format). #### Response Example ```json { "media_files": [ { "type": "recording", "url": "https://example.com/recordings/session_123.mp4", "format": "mp4" }, { "type": "transcript", "url": "https://example.com/transcripts/session_123.txt", "format": "txt" } ] } ``` ``` -------------------------------- ### Handle Zoom RTMS Started Webhook Source: https://github.com/attendee-labs/attendee/blob/main/docs/zoom_rtms.md Forward the Zoom `meeting.rtms_started` webhook payload to Attendee by calling `POST /api/v1/app_sessions`. Set the `zoom_rtms` field to the webhook payload. Metadata, transcription, and recording settings can also be specified. ```javascript const express = require('express'); const app = express(); app.post('/zoom/webhook', express.json(), async (req, res) => { const payload = req.body; if (payload.event === 'meeting.rtms_started') { // Forward the webhook payload to Attendee await fetch('https://api.attendee.io/api/v1/app_sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer YOUR_ATTENDEE_API_KEY` }, body: JSON.stringify({ zoom_rtms: payload, // Optional: specify metadata, transcription, and recording settings // metadata: { ... }, // transcription: { ... }, // recording: { ... } }) }); } res.sendStatus(200); }); app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` -------------------------------- ### Build the Attendee Docker image Source: https://github.com/attendee-labs/attendee/blob/main/README.md Initializes the development environment by building the required Docker containers. ```bash docker compose -f dev.docker-compose.yaml build ``` -------------------------------- ### GET /api/v1/bots/ Source: https://github.com/attendee-labs/attendee/blob/main/README.md Poll the status of a specific bot instance. ```APIDOC ## GET /api/v1/bots/ ### Description Retrieve the current state and transcription status of a specific bot. ### Method GET ### Endpoint /api/v1/bots/ ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the bot. ### Response #### Success Response (200) - **id** (string) - The unique identifier for the bot. - **meeting_url** (string) - The URL of the meeting. - **state** (string) - The current state of the bot (e.g., ended). - **transcription_state** (string) - The status of the transcription process (e.g., complete). #### Response Example { "id": "bot_3hfP0PXEsNinIZmh", "meeting_url": "https://us05web.zoom.us/j/88669088234?pwd=...", "state": "ended", "transcription_state": "complete" } ``` -------------------------------- ### Project Usage URL Parameters Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_usage.html Demonstrates how to construct URLs for filtering project usage data by interval, measure, platform, and category set. ```html [Months](?interval=months&measure={{ measure }}{% if platform %}&platform={{ platform }}{% endif %}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Weeks](?interval=weeks&measure={{ measure }}{% if platform %}&platform={{ platform }}{% endif %}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Days](?interval=days&measure={{ measure }}{% if platform %}&platform={{ platform }}{% endif %}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Count](?interval={{ interval }}&measure=count{% if platform %}&platform={{ platform }}{% endif %}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Time](?interval={{ interval }}&measure=time{% if platform %}&platform={{ platform }}{% endif %}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Percent](?interval={{ interval }}&measure=percent{% if platform %}&platform={{ platform }}{% endif %}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [All](?interval={{ interval }}&measure={{ measure }}{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Zoom](?interval={{ interval }}&measure={{ measure }}&platform=zoom{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Google Meet](?interval={{ interval }}&measure={{ measure }}&platform=meet{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) [Teams](?interval={{ interval }}&measure={{ measure }}&platform=teams{% if category_set != 'default' %}&category_set={{ category_set }}{% endif %}) {% if show_category_selector %} [Default](?interval={{ interval }}&measure={{ measure }}{% if platform %}&platform={{ platform }}{% endif %}) [Transcript](?interval={{ interval }}&measure={{ measure }}{% if platform %}&platform={{ platform }}{% endif %}&category_set=transcript) {% endif %} ``` -------------------------------- ### GET /api/v1/bots//transcript Source: https://github.com/attendee-labs/attendee/blob/main/README.md Retrieve the meeting transcript for a specific bot. ```APIDOC ## GET /api/v1/bots//transcript ### Description Retrieve the meeting transcripts. This can be called while the meeting is in progress for partial transcripts or after it ends for the full transcript. ### Method GET ### Endpoint /api/v1/bots//transcript ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the bot. ### Response #### Success Response (200) - **speaker_name** (string) - Name of the speaker. - **speaker_uuid** (string) - Unique ID of the speaker. - **speaker_user_uuid** (string) - User UUID of the speaker. - **timestamp_ms** (integer) - Timestamp in milliseconds. - **duration_ms** (integer) - Duration of the segment in milliseconds. - **transcription** (string) - The transcribed text. #### Response Example [ { "speaker_name": "Noah Duncan", "speaker_uuid": "16778240", "speaker_user_uuid": "AAB6E21A-6B36-EA95-58EC-5AF42CD48AF8", "timestamp_ms": 1079, "duration_ms": 7710, "transcription": "You can totally record this, buddy." } ] ``` -------------------------------- ### Initialize local environment variables Source: https://github.com/attendee-labs/attendee/blob/main/README.md Generates a .env file for local configuration based on the host operating system. ```bash docker compose -f dev.docker-compose.yaml run --rm attendee-app-local python init_env.py > .env ``` ```powershell docker compose -f dev.docker-compose.yaml run --rm attendee-app-local python init_env.py | Out-File -Encoding utf8 .env ``` -------------------------------- ### GET /api/v1/app_sessions/{id}/participant_events Source: https://github.com/attendee-labs/attendee/blob/main/docs/zoom_rtms.md Retrieve participant join/leave events for the app session. ```APIDOC ## GET /api/v1/app_sessions/{id}/participant_events ### Description Returns participant join and leave events for the app session. This endpoint is only available after the session has moved to the `ended` state. ### Method GET ### Endpoint /api/v1/app_sessions/{id}/participant_events ### Parameters #### Path Parameters - **id** (string) - Required - The ID of the app session. ### Response #### Success Response (200) - **participant_events** (array) - An array of objects, each representing a participant event (e.g., join, leave, with timestamp and participant information). #### Response Example ```json { "participant_events": [ { "timestamp": "2023-10-27T10:00:00Z", "event_type": "join", "participant_name": "John Doe" }, { "timestamp": "2023-10-27T10:30:00Z", "event_type": "leave", "participant_name": "Jane Smith" } ] } ``` ``` -------------------------------- ### Initialize Bot Login Groups Page Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Sets up the main bot login groups page by initializing various components like modals, action buttons, tabs, and dynamic content. ```javascript function initializeBotLoginGroupsPage() { initializeSharedEditGroupModal(); initializeSharedDeleteEntityModal(); initializeSharedActionButtons(); initializeCreateGroupModal(); initializePlatformTabs(); restoreActivePlatform(); initializeDynamicBotLoginContent(document); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initializeBotLoginGroupsPage); } else { initializeBotLoginGroupsPage(); } ``` -------------------------------- ### Initialize Billing Form Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_billing.html Sets up event listeners to update credit calculations dynamically based on user input. ```javascript // Function that contains all your billing logic function initializeBillingForm() { const purchaseAmountInput = document.getElementById('purchaseAmount'); const calculatedCredits = document.getElementById('calculatedCredits'); // Don't proceed if elements aren't found (prevents errors) if (!purchaseAmountInput || !calculatedCredits) return; // Function to update the credits based on purchase amount function updateCredits() { const amount = parseFloat(purchaseAmountInput.value) || 0; const credits = calculateCredits(amount); calculatedCredits.textContent = Math.floor(credits); } // Update credits on input change purchaseAmountInput.addEventListener('input', updateCredits); // Initialize with default value updateCredits(); } ``` -------------------------------- ### GET /api/v1/app_sessions/{id}/transcript Source: https://github.com/attendee-labs/attendee/blob/main/docs/zoom_rtms.md Retrieve the full transcript for a completed app session. ```APIDOC ## GET /api/v1/app_sessions/{id}/transcript ### Description Returns the full transcript for a completed app session. This endpoint is only available after the session has moved to the `ended` state. ### Method GET ### Endpoint /api/v1/app_sessions/{id}/transcript ### Parameters #### Path Parameters - **id** (string) - Required - The ID of the app session. ### Response #### Success Response (200) - **transcript** (string) - The full transcript of the app session. #### Response Example ```json { "transcript": "This is the full transcript of the meeting..." } ``` ``` -------------------------------- ### Create Bot with Voice Agent and Screen Share Source: https://github.com/attendee-labs/attendee/blob/main/docs/voice_agents.md Configure a bot to include both a voice agent and a screen share by adding the `screenshare_url` to the `voice_agent_settings`. The `screenshare_url` should point to a publicly accessible page. ```json { "meeting_url": "https://meet.google.com/abc-def-ghi", "bot_name": "Avatar Bot", "voice_agent_settings": { "url": "https://your-voice-agent-app.com?agent_id=1234567890", "screenshare_url": "https://your-app.com/screen" } } ``` -------------------------------- ### Initialize Create Group Modal Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Initializes the create bot login group modal, setting up submit and event handlers for showing and hiding the modal. Ensures handlers are attached only once. ```javascript function initializeCreateGroupModal() { const modalElement = document.getElementById("createBotLoginGroupModal"); const form = document.getElementById("create-bot-login-group-form"); if (!modalElement || !form) { return; } if (form.dataset.submitHandlerAttached !== "true") { form.addEventListener("submit", async function(event) { event.preventDefault(); await submitSectionForm({ form, modalId: "createBotLoginGroupModal", targetId: form.dataset.targetId, resetAfterSuccess: true, onSuccess: configureCreateGroupForm, }); }); form.dataset.submitHandlerAttached = "true"; } if (modalElement.dataset.showHandlerAttached !== "true") { modalElement.addEventListener("show.bs.modal", function() { setModalError(form, ""); configureCreateGroupForm(); }); modalElement.dataset.showHandlerAttached = "true"; } if (modalElement.dataset.clearHandlerAttached !== "true") { modalElement.addEventListener("hidden.bs.modal", function() { resetModalForm("createBotLoginGroupModal"); setModalError(form, ""); configureCreateGroupForm(); }); modalElement.dataset.clearHandlerAttached = "true"; } configureCreateGroupForm(); } ``` -------------------------------- ### Initialize Dynamic Bot Login Content Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Initializes dynamic content for bot logins, including file preview handlers, create forms, and modal reset logic for different platforms. ```javascript function initializeDynamicBotLoginContent(root = document) { attachFilePreviewHandlers("private_key_file_", "private_key_", "private_key_preview_", "private_key_preview_content_", root); attachFilePreviewHandlers("cert_file_", "cert_", "cert_preview_", "cert_preview_content_", root); initializeCreateBotLoginForms(root); attachModalHiddenReset("teamsBotLoginCredentialsModal-", function(suffix) { resetModalForm(`teamsBotLoginCredentialsModal-${suffix}`); const form = document.querySelector(`#teamsBotLoginCredentialsModal-${suffix} form`); if (form) { setModalError(form, ""); } }, root); attachModalHiddenReset("googleMeetBotLoginModal-", function(suffix) { resetModalForm(`googleMeetBotLoginModal-${suffix}`); clearGoogleMeetCredentialFields(suffix); const form = document.querySelector(`#googleMeetBotLoginModal-${suffix} form`); if (form) { setModalError(form, ""); } }, root); } ``` -------------------------------- ### Deepgram Transcription Configuration Source: https://github.com/attendee-labs/attendee/blob/main/docs/transcription.md Example of setting transcription parameters for Deepgram, specifying the language and model. This is used within the `transcription_settings` object of a 'create bot' request. ```json { "deepgram": { "language": "en-US", "model": "nova-2" } } ``` -------------------------------- ### Minimal Custom Async Transcription Configuration Source: https://github.com/attendee-labs/attendee/blob/main/docs/custom_async_transcription.md This minimal JSON configuration demonstrates how to enable the custom async transcription provider without any additional custom parameters. ```json { "meeting_url": "https://zoom.us/j/123456789", "bot_name": "My Bot", "transcription_settings": { "custom_async_v2": {} } } ``` -------------------------------- ### Get First N Lines of Text Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Helper function to extract the first N lines from a given text. Used for displaying a snippet of file content in previews. ```javascript function getFirstNLines(text, n) { const lines = text.split("\n"); const firstLines = lines.slice(0, n); return firstLines.join("\n"); } ``` -------------------------------- ### Show Project Creation Modal Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/sidebar.html Displays the modal dialog for creating a new project. This function is triggered when the 'Create new project' button is clicked. ```javascript function createNewProject() { // Show the project creation modal const createProjectModal = new bootstrap.Modal(document.getElementById('createProjectModal')); createProjectModal.show(); } ``` -------------------------------- ### Run database migrations Source: https://github.com/attendee-labs/attendee/blob/main/README.md Executes Django migrations within the running attendee-app-local container. ```bash docker compose -f dev.docker-compose.yaml exec attendee-app-local python manage.py migrate ``` -------------------------------- ### Poll Bot Status with Attendee API Source: https://github.com/attendee-labs/attendee/blob/main/README.md Make a GET request to this endpoint to check the current state of your bot in a meeting. The response indicates if the meeting has ended and if transcription is complete. ```curl curl -X GET https://app.attendee.dev/api/v1/bots/bot_3hfP0PXEsNinIZmh \ -H 'Authorization: Token ' \ -H 'Content-Type: application/json' ``` -------------------------------- ### Calendar Events Update Webhook Payload Source: https://github.com/attendee-labs/attendee/blob/main/docs/webhooks.md This payload is triggered by 'calendar.events_update' and provides calendar sync status. Fetch calendar events from the Attendee API after receiving this webhook to get the latest information. ```json { "state": , "connection_failure_data": , "last_successful_sync_at": , "last_attempted_sync_at": } ``` -------------------------------- ### Initialize Speaker Timeline Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/partials/project_bot_recordings.html Initializes the speaker timeline by positioning bars and setting up interactions. It relies on the video's duration to calculate bar positions and updates a playhead indicator. ```javascript document.querySelectorAll('.speaker-timeline-wrapper').forEach(function(wrapper) { var recordingItem = wrapper.closest('.recording-item'); var video = recordingItem ? recordingItem.querySelector('video') : null; if (!video) return; var tracksCol = wrapper.querySelector('.speaker-timeline-tracks'); var playhead = wrapper.querySelector('.speaker-timeline-playhead'); if (!tracksCol) return; function initTimeline(durationMs) { // Position bars based on video duration tracksCol.querySelectorAll('.speaker-timeline-bar').forEach(function(bar) { var startMs = parseFloat(bar.dataset.startMs); var endMs = bar.dataset.endMs != null ? parseFloat(bar.dataset.endMs) : durationMs; var startPct = Math.max(0, (startMs / durationMs) * 100); var widthPct = Math.max(0, Math.min(100 - startPct, ((endMs - startMs) / durationMs) * 100)); bar.style.left = startPct + '%'; bar.style.width = widthPct + '%'; }); // Click anywhere on the tracks area to seek tracksCol.addEventListener('click', function(e) { var rect = tracksCol.getBoundingClientRect(); var pct = (e.clientX - rect.left) / rect.width; video.currentTime = (pct * durationMs) / 1000; video.play(); }); // Update playhead position on video timeupdate video.addEventListener('timeupdate', function() { var pct = (video.currentTime * 1000) / durationMs; playhead.style.left = (pct * 100) + '%'; }); } if (video.readyState >= 1) { initTimeline(video.duration * 1000); } else { video.addEventListener('loadedmetadata', function() { initTimeline(video.duration * 1000); }); } }); ``` -------------------------------- ### Retrieve Attendee Bot Transcript using cURL Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_dashboard.html This cURL command retrieves the transcript for a specific bot. Replace `` with the ID of the bot you want to get the transcript from and `` with your API key. ```bash curl -X GET \ {{ request.scheme }}://{{ request.get_host }}/api/v1/bots//transcript \ -H 'Authorization: Token ' -H 'Content-Type: application/json' ``` -------------------------------- ### Configure Realtime Video Streaming Source: https://github.com/attendee-labs/attendee/blob/main/docs/realtime_video.md Configure the websocket settings to enable per-participant video streaming. You can set webcam and screenshare resolutions independently. Supported values are "360p", "720p", "1080p", and "none". Both default to "360p". ```json { "meeting_url": "https://us06web.zoom.us/j/12345678", "bot_name": "Video Bot", "websocket_settings": { "per_participant_video": { "url": "wss://your-server.com/attendee-websocket", "webcam_resolution": "360p", "screenshare_resolution": "720p" } } } ``` -------------------------------- ### Retrieve Meeting Transcripts with Attendee API Source: https://github.com/attendee-labs/attendee/blob/main/README.md Once a meeting has ended and transcription is complete, use this GET request to retrieve the full meeting transcripts. Partial transcripts can also be retrieved while the meeting is in progress. ```curl curl -X GET https://app.attendee.dev/api/v1/bots/bot_3hfP0PXEsNinIZmh/transcript \ -H 'Authorization: Token mpc67dedUlzEDXfNGZKyC30t6cA11TYh' \ -H 'Content-Type: application/json' ``` -------------------------------- ### Create a new branch Source: https://github.com/attendee-labs/attendee/blob/main/CONTRIBUTING.md Command to create and switch to a new feature branch. ```bash git switch -c feature/your-feature-name ``` -------------------------------- ### Initialize Edit Form JavaScript Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_team.html Initializes the edit user form, setting up event listeners for form submission and UI updates. This snippet is a starting point for the edit form's JavaScript functionality. ```javascript function initializeEditForm() { const editForm = document.getElementById('editUserForm'); const editSubmitBtn = document.getElementById('editSubmitBtn'); const editSpinner = editSubmitBtn.querySelector('.spinner-border'); const editE ``` -------------------------------- ### Initialize Create Bot Login Forms Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Initializes submit event listeners for forms with the class 'js-create-bot-login-form'. Ensures handlers are attached only once. ```javascript function initializeCreateBotLoginForms(root = document) { root.querySelectorAll(".js-create-bot-login-form").forEach((form) => { if (form.dataset.submitHandlerAttached === "true") { return; } form.addEventListener("submit", async function(event) { event.preventDefault(); await submitSectionForm({ form, modalId: form.dataset.modalId, targetId: form.dataset.targetId, validate: form.dataset.requiresGoogleMeetValidation === "true" ? validateGoogleMeetBotLoginForm : null, }); }); form.dataset.submitHandlerAttached = "true"; }); } ``` -------------------------------- ### Create a Scheduled Bot Source: https://github.com/attendee-labs/attendee/blob/main/docs/scheduled_bots.md Include the `join_at` parameter in the bot creation API call to schedule a bot. Ensure the `join_at` time is in the future and at least 2 minutes from the current time. The format must be ISO 8601. ```json { "meeting_url": "https://zoom.us/j/123456789", "bot_name": "My scheduled bot", "join_at": "2025-06-16T16:24:00+0000" } ``` -------------------------------- ### Launch Zoom Bot with Onbehalf Token Source: https://github.com/attendee-labs/attendee/blob/main/docs/zoom_oauth.md Specify the Zoom user the bot is joining on behalf of by passing the user's Zoom user ID in the `zoom_settings.onbehalf_token.zoom_oauth_connection_user_id` parameter when launching the bot. ```javascript const response = await attendee.api("/api/launch-bot", { method: "POST", body: { // ... other parameters zoom_settings: { onbehalf_token: { zoom_oauth_connection_user_id: "USER_ID_HERE" } } } }); ``` -------------------------------- ### POST /bots Source: https://github.com/attendee-labs/attendee/blob/main/docs/scheduled_bots.md Create a new scheduled bot by providing a meeting URL and a future join time. ```APIDOC ## POST /bots ### Description Creates a new bot scheduled to join a meeting at a specific time in the future. ### Method POST ### Endpoint /bots ### Parameters #### Request Body - **meeting_url** (string) - Required - The URL of the meeting to join. - **bot_name** (string) - Optional - A name for the bot. - **join_at** (string) - Required - The time to join in ISO 8601 format (must be in the future). ### Request Example { "meeting_url": "https://zoom.us/j/123456789", "bot_name": "My scheduled bot", "join_at": "2025-06-16T16:24:00+0000" } ### Response #### Success Response (200) - **id** (string) - The unique identifier of the bot. - **meeting_url** (string) - The meeting URL. - **state** (string) - The current state of the bot (e.g., "scheduled"). - **join_at** (string) - The scheduled join time. #### Response Example { "id": "bot_HiXYgjyeWmTVOaII", "meeting_url": "https://meet.google.com/gvy-zzra-ktd", "state": "scheduled", "join_at": "2025-06-16T16:55:00Z" } ``` -------------------------------- ### Initialize Platform Tabs Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Adds event listeners to platform tab elements to store the active platform when a tab is shown. Ensures handlers are attached only once. ```javascript function initializePlatformTabs() { document.querySelectorAll("#bot-login-platform-tabs [data-bs-toggle=\"tab\"]").forEach((tab) => { if (tab.dataset.platformHandlerAttached === "true") { return; } tab.addEventListener("shown.bs.tab", function(event) { storeActivePlatform(event.target.dataset.platform); configureCreateGroupForm(); }); tab.dataset.platformHandlerAttached = "true"; }); } ``` -------------------------------- ### POST /api/v1/bots Source: https://github.com/attendee-labs/attendee/blob/main/README.md Join a meeting by creating a new bot instance. ```APIDOC ## POST /api/v1/bots ### Description Join a meeting by initiating a bot with the provided meeting URL. ### Method POST ### Endpoint /api/v1/bots ### Request Body - **meeting_url** (string) - Required - The URL of the meeting to join. - **bot_name** (string) - Required - The display name for the bot. ### Request Example { "meeting_url": "https://us05web.zoom.us/j/84315220467?pwd=...", "bot_name": "My Bot" } ### Response #### Success Response (200) - **id** (string) - The unique identifier for the bot. - **meeting_url** (string) - The URL of the meeting. - **state** (string) - The current state of the bot (e.g., joining). - **transcription_state** (string) - The status of the transcription process. #### Response Example { "id": "bot_3hfP0PXEsNinIZmh", "meeting_url": "https://us05web.zoom.us/j/4849920355?pwd=...", "state": "joining", "transcription_state": "not_started" } ``` -------------------------------- ### Initialize Billing and Autopay Forms Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_billing.html Calls functions to initialize billing and autopay settings. This code should be executed on regular page load and after HTMX content is swapped. ```javascript document.addEventListener('DOMContentLoaded', function() { initializeBillingForm(); initializeAutopayForm(); }); document.addEventListener('htmx:afterSwap', function() { initializeBillingForm(); initializeAutopayForm(); }); ``` -------------------------------- ### Configuration with Custom Async Transcription Parameters Source: https://github.com/attendee-labs/attendee/blob/main/docs/custom_async_transcription.md This JSON configuration shows how to specify custom headers and form data for the custom async transcription provider when creating a bot. ```json { "meeting_url": "https://zoom.us/j/123456789", "bot_name": "My Bot", "transcription_settings": { "custom_async_v2": { "headers": { "X-Custom-Header": "value" }, "form_data": { "language": "fr-FR", "model": "whisper-large-v3", "custom_param": "any_value" } } } } ``` -------------------------------- ### Rebuild Attendee Docker Image Source: https://github.com/attendee-labs/attendee/blob/main/docs/faq.md Commands to rebuild the local development environment when encountering issues joining Zoom meetings. ```bash docker compose -f dev.docker-compose.yaml build ``` ```makefile make build ``` -------------------------------- ### Configure Realtime Audio Streaming Source: https://github.com/attendee-labs/attendee/blob/main/docs/realtime_audio.md Set the websocket_settings.audio parameter when creating a bot to enable mixed audio streaming. ```json { "meeting_url": "https://meet.google.com/abc-def-ghi", "bot_name": "Audio Bot", "websocket_settings": { "audio": { "url": "wss://your-server.com/attendee-websocket", "sample_rate": 16000 } } } ``` -------------------------------- ### Initialize Invite Form JavaScript Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_team.html Initializes the invite user form, handling role toggles, project selection validation, and form submission with asynchronous fetching. It manages UI updates, error messages, and success alerts. ```javascript function initializeInviteForm() { const inviteForm = document.getElementById('inviteUserForm'); const submitBtn = document.getElementById('inviteSubmitBtn'); const spinner = submitBtn.querySelector('.spinner-border'); const inviteError = document.getElementById('inviteError'); const successAlert = document.getElementById('successAlert'); const errorAlert = document.getElementById('errorAlert'); const roleToggle = document.getElementById('roleToggle'); const roleLabel = document.getElementById('roleLabel'); const roleDescription = document.getElementById('roleDescription'); const projectAccessSection = document.getElementById('projectAccessSection'); const projectCheckboxes = document.querySelectorAll('.project-checkbox'); const projectSelectionError = document.getElementById('projectSelectionError'); function updateRoleUI() { if (roleToggle.checked) { // Admin role selected roleLabel.textContent = 'Administrator'; roleDescription.textContent = 'This user will be able to create projects, purchase credits, invite teammates and have access to all projects in your account.'; projectAccessSection.style.display = 'none'; projectSelectionError.classList.add('d-none'); // Clear all project selections projectCheckboxes.forEach(checkbox => checkbox.checked = false); } else { // Regular user selected roleLabel.textContent = 'Regular User'; roleDescription.textContent = 'This user will have access only to selected projects.'; projectAccessSection.style.display = 'block'; } } function validateProjectSelection() { if (!roleToggle.checked) { // Only validate for regular users const hasSelectedProject = Array.from(projectCheckboxes).some(checkbox => checkbox.checked); if (!hasSelectedProject) { projectSelectionError.classList.remove('d-none'); return false; } else { projectSelectionError.classList.add('d-none'); return true; } } return true; } // Initialize role UI updateRoleUI(); // Handle role toggle changes roleToggle.addEventListener('change', updateRoleUI); // Handle project selection changes for validation projectCheckboxes.forEach(checkbox => { checkbox.addEventListener('change', validateProjectSelection); }); function showAlert(type, message) { const alert = type === 'success' ? successAlert : errorAlert; const messageElement = type === 'success' ? document.getElementById('successMessage') : document.getElementById('errorMessage'); messageElement.textContent = message; alert.classList.remove('d-none'); // Scroll to top to show alert window.scrollTo({ top: 0, behavior: 'smooth' }); } function hideInlineError() { inviteError.classList.add('d-none'); projectSelectionError.classList.add('d-none'); } function showInlineError(message) { inviteError.textContent = message; inviteError.classList.remove('d-none'); } function setLoading(loading) { if (loading) { submitBtn.disabled = true; spinner.classList.remove('d-none'); } else { submitBtn.disabled = false; spinner.classList.add('d-none'); } } inviteForm.addEventListener('submit', function(e) { e.preventDefault(); hideInlineError(); // Validate project selection if (!validateProjectSelection()) { return; } setLoading(true); const formData = new FormData(inviteForm); fetch(inviteForm.action, { method: 'POST', body: formData, headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken'), } }) .then(response => { if (response.ok) { return response.text().then(text => ({ success: true, message: text })); } else { return response.text().then(text => ({ success: false, message: text })); } }) .then(result => { setLoading(false); if (result.success) { // Close modal const modal = bootstrap.Modal.getInstance(document.getElementById('inviteUserModal')); modal.hide(); // Reset form inviteForm.reset(); updateRoleUI(); // Reset the UI state // Show success alert showAlert('success', result.message); // Refresh page after short delay to show updated team list setTimeout(() => { window.location.reload(); }, 2000); } else { // Show error in modal showInlineError(result.message); } }) .catch(error => { setLoading(false); console.error('Error:', error); showInlineError('An unexpected error occurred. Please try again.'); }); }); // Hide alerts when modal is opened document.getElementById('inviteUserModal').addEventListener('show.bs.modal', function() { successAlert.classList.add('d-none'); errorAlert.classList.add('d-none'); hideInlineError(); // Reset form and UI when modal opens inviteForm.reset(); updateRoleUI(); }); }; ``` -------------------------------- ### Configure Create Group Form Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Sets the value of the platform input and the target ID for the create bot login group form based on the active platform. ```javascript function configureCreateGroupForm() { const form = document.getElementById("create-bot-login-group-form"); const platformInput = document.getElementById("create-bot-login-group-platform"); const title = document.getElementById("create-bot-login-group-modal-title"); if (!form || !platformInput) { return; } const platform = window.activeBotLoginPlatform || "google\_meet"; platformInput.value = platform; form.dataset.targetId = platform === "google\_meet" ? "google-meet-bot-login-section" : "teams-bot-login-section"; if (title) { title.textContent = `Create ${getPlatformLabel(platform)} Login Group`; } } ``` -------------------------------- ### Render Invitation Form Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_team.html Displays a form to invite new users with role selection and project access assignment. ```django {% csrf_token %} Email Address User Role Regular User This user will have access only to selected projects. Project Access * {% for project in projects %} {{ project.name }} {% endfor %} Select at least one project that this user can access. Please select at least one project. Cancel Send Invitation ``` -------------------------------- ### Initialize Autopay Form Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_billing.html Manages visibility of autopay settings, updates display values, and handles API submission for autopay configuration. ```javascript // Function to initialize autopay form function initializeAutopayForm() { const autopayEnabled = document.getElementById('autopayEnabled'); const autopaySettings = document.getElementById('autopaySettings'); const autopayThreshold = document.getElementById('autopayThreshold'); const autopayAmount = document.getElementById('autopayAmount'); const amountDisplay = document.getElementById('amountDisplay'); const autopayCalculatedCredits = document.getElementById('autopayCalculatedCredits'); const saveAutopayBtn = document.getElementById('saveAutopayBtn'); // Don't proceed if elements aren't found if (!autopayEnabled || !autopaySettings) return; // Toggle autopay settings visibility function toggleAutopaySettings() { if (autopayEnabled.checked) { autopaySettings.style.display = 'block'; } else { autopaySettings.style.display = 'none'; } } // Update display values function updateDisplayValues() { if (amountDisplay && autopayAmount) { amountDisplay.textContent = parseFloat(autopayAmount.value || 0).toFixed(2); } if (autopayCalculatedCredits && autopayAmount) { const amount = parseFloat(autopayAmount.value || 0); const credits = calculateCredits(amount); autopayCalculatedCredits.textContent = Math.floor(credits); } } // Add event listeners if (autopayEnabled) { autopayEnabled.addEventListener('change', toggleAutopaySettings); } if (autopayAmount) { autopayAmount.addEventListener('input', updateDisplayValues); } // Function to show/hide error alert function showAutopayError(message) { const errorAlert = document.getElementById('autopayErrorAlert'); const errorMessage = document.getElementById('autopayErrorMessage'); if (errorAlert && errorMessage) { errorMessage.textContent = message; errorAlert.classList.remove('d-none'); } } function hideAutopayError() { const errorAlert = document.getElementById('autopayErrorAlert'); if (errorAlert) { errorAlert.classList.add('d-none'); } } // Hide error when modal is opened const autopayModal = document.getElementById('autopayModal'); if (autopayModal) { autopayModal.addEventListener('shown.bs.modal', function() { hideAutopayError(); }); } // Handle save button click if (saveAutopayBtn) { saveAutopayBtn.addEventListener('click', async function() { // Hide any previous errors hideAutopayError(); const enabled = autopayEnabled.checked; const threshold = 10; // Fixed threshold value const amount = parseFloat(autopayAmount.value) || 0; const data = { autopay_enabled: enabled }; if (enabled) { data.autopay_threshold_credits = threshold; data.autopay_amount_dollars = amount; } try { const response = await fetch(`{% url 'projects:project-autopay' project.object_id %}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value }, body: JSON.stringify(data) }); if (response.ok) { // Close modal and ``` -------------------------------- ### Initialize Shared Delete Entity Modal Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Configures the submit handler for the shared delete bot login entity modal. Ensures the handler is attached only once. ```javascript function initializeSharedDeleteEntityModal() { const form = document.getElementById("delete-bot-login-entity-form"); if (!form) { return; } if (form.dataset.submitHandlerAttached !== "true") { form.addEventListener("submit", async function(event) { event.preventDefault(); await submitSectionPost({ form, modalId: "deleteBotLoginEntityModal", targetId: form.dataset.targetId, }); }); form.dataset.submitHandlerAttached = "true"; } } ``` -------------------------------- ### Configure Bot Launch Timeout Source: https://github.com/attendee-labs/attendee/blob/main/docs/zoom_oauth.md Configure the timeout for when a bot attempts to join a meeting but the `onbehalf` user is not present. This parameter `automatic_leave_settings.authorized_user_not_in_meeting_timeout_seconds` defaults to 600 seconds. ```javascript const response = await attendee.api("/api/launch-bot", { method: "POST", body: { // ... other parameters automatic_leave_settings: { authorized_user_not_in_meeting_timeout_seconds: 300 // Example: 5 minutes } } }); ``` -------------------------------- ### Create Attendee Bot using cURL Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_dashboard.html Use this cURL command to create a new bot in the Attendee API. Ensure you replace `` with your actual API key and provide the correct meeting URL and bot name. ```bash curl -X POST \ {{ request.scheme }}://{{ request.get_host }}/api/v1/bots \ -H 'Authorization: Token ' -H 'Content-Type: application/json' \ -d '{"meeting_url": "https://us05web.zoom.us/j/xxxxxxx?pwd=xxxxxx", "bot_name": "My Bot"}' ``` -------------------------------- ### Store Active Platform Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Stores the currently active bot login platform in a global variable. ```javascript function storeActivePlatform(platform) { window.activeBotLoginPlatform = platform; } ``` -------------------------------- ### Configure Teams Bot Login Source: https://github.com/attendee-labs/attendee/blob/main/docs/signed_in_bots.md Activate Teams bot login by passing the 'use_login' parameter. Optionally control login mode and specify a login group name. ```json "teams_settings": {"use_login": true} ``` ```json "teams_settings": {"use_login": true, "login_mode": "only_if_required"} ``` ```json "teams_settings": {"use_login": true, "login_group_name": "Customer A"} ``` -------------------------------- ### Create Bot with Voice Agent Settings Source: https://github.com/attendee-labs/attendee/blob/main/docs/voice_agents.md Use this JSON payload to create a bot that includes voice agent settings. Provide the URL of your voice agent web page in the `voice_agent_settings.url` field. ```json { "meeting_url": "https://meet.google.com/abc-def-ghi", "bot_name": "Avatar Bot", "voice_agent_settings": { "url": "https://your-voice-agent-app.com?agent_id=1234567890" } } ``` -------------------------------- ### Handle HTMX After Swap Event for Bot Logins Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_bot_logins.html Initializes dynamic bot login content after HTMX swaps content in the Google Meet or Teams sections. This ensures new content is interactive. ```javascript document.body.addEventListener("htmx:afterSwap", function(event) { if (event.detail.target.id === "google-meet-bot-login-section" || event.detail.target.id === "teams-bot-login-section") { initializeDynamicBotLoginContent(event.detail.target); } }); ``` -------------------------------- ### Initialize and Submit User Edit Form Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/project_team.html Handles populating the edit modal with user data and processing form submissions using the Fetch API. Requires Bootstrap modal instances and CSRF token handling. ```javascript with user data document.getElementById('editUserObjectId').value = userObjectId; document.getElementById('editUserEmail').textContent = userEmail; editRoleToggle.checked = userRole === 'admin'; // Initialize hidden input with current active status editUserActiveInput.value = userActive ? 'true' : 'false'; // Update UI updateEditRoleUI(); updateActiveButton(); // Clear all project checkboxes first editProjectCheckboxes.forEach(checkbox => checkbox.checked = false); // Populate current project access for regular users if (userRole !== 'admin' && userProjectAccess) { const projectIds = userProjectAccess.split(',').filter(id => id.trim()); projectIds.forEach(projectId => { const checkbox = document.getElementById(`edit_project_${projectId.trim()}`); if (checkbox) { checkbox.checked = true; } }); } hideEditInlineError(); }); }); editForm.addEventListener('submit', function(e) { e.preventDefault(); hideEditInlineError(); // Validate project selection if (!validateEditProjectSelection()) { return; } setEditLoading(true); // For normal save, ensure the hidden input has the current active status editUserActiveInput.value = currentUserActive ? 'true' : 'false'; const formData = new FormData(editForm); fetch(editForm.action, { method: 'POST', body: formData, headers: { 'X-CSRFToken': formData.get('csrfmiddlewaretoken'), } }) .then(response => { if (response.ok) { return response.text().then(text => ({ success: true, message: text })); } else { return response.text().then(text => ({ success: false, message: text })); } }) .then(result => { setEditLoading(false); if (result.success) { // Close modal const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal')); modal.hide(); // Refresh page immediately without showing success alert window.location.reload(); } else { // Show error in modal showEditInlineError(result.message); } }) .catch(error => { setEditLoading(false); console.error('Error:', error); showEditInlineError('An unexpected error occurred. Please try again.'); }); }); // Hide alerts when edit modal is opened document.getElementById('editUserModal').addEventListener('show.bs.modal', function() { successAlert.classList.add('d-none'); errorAlert.classList.add('d-none'); hideEditInlineError(); }); } // Call when HTMX content is loaded document.addEventListener('htmx:afterSwap', function() { initializeInviteForm(); initializeEditForm(); }); document.addEventListener('DOMContentLoaded', function() { initializeInviteForm(); initializeEditForm(); }); {% endblock %} ``` -------------------------------- ### Navigate to Selected Project Source: https://github.com/attendee-labs/attendee/blob/main/bots/templates/projects/sidebar.html Redirects the user to the selected project's URL by replacing the current project ID in the URL with the new project ID. This function is used for switching between projects. ```javascript function selectProject(projectId) { const currentProjectId = "{{ project.object_id }}"; const currentPath = window.location.pathname; const newPath = currentPath.replace(currentProjectId, projectId); window.location.href = newPath; } ```