Unable to invite external contacts to meetings using the In-meeting controls API

Hi @drake , this is expected behavior for this endpoint because the invitee must a user that belongs to the host’s account.


The language in the documentation could be clearer so that added external contacts are not interpreted as “a part of the meeting host’s account”. I’ve drafted up a feature request for you, but in the interim, here are your options:

Option 1 — Pre-register the external user (recommended)

Use the Add Meeting Registrant API before the meeting starts. The registrant receives a personal join URL via email, which they can use to join when invited.

1POST /v2/meetings/{meetingId}/registrants
2Content-Type: application/json
3Authorization: Bearer {token}
4
5{
6  "email": "external-user@gmail.com",
7  "first_name": "External",
8  "last_name": "User"
9}

Option 2 — Share the meeting join link directly

Retrieve the meeting’s join_url via GET /v2/meetings/{meetingId} and distribute it to the external user through your own notification channel (email, Team Chat, etc.). This bypasses the API invite restriction entirely. You can pair this with the meeting.started webhook to create a custom automation or use Zoom Workflows to create a plain english one. Below is the JSON for the sample Zoom Workflow I created that you can import into your own and modify for your needs:

{
  "version": 1,
  "exportedAt": "2026-07-15T17:03:08.466Z",
  "data": {
    "metadata": {
      "flowBookId": "",
      "name": "Meeting Join Link Sender",
      "description": "When a meeting starts, this workflow sends the meeting join link to external contacts.",
      "source": "cui",
      "sessionId": "" // REDACTED
    },
    "flows": [
      {
        "workflow_metadata": {
          "flow_id": "",
          "name": "Meeting Join Link Sender",
          "description": "When a meeting starts, this workflow sends the meeting join link to external contacts."
        },
        "nodes": [
          {
            "node_id": "zm.meeting.meeting.meeting_event",
            "reference_id": "EFTEST",
            "node_type": "trigger",
            "node_name": "Meeting event",
            "node_alias": "Meeting Started Trigger",
            "node_desc": "Start when a meeting event occurs, such as meeting start, end, or other activities.",
            "input_map": {
              "filter_type": "all_meetings",
              "time_range_filter": {}
            },
            "config": {
              "event_name": "meeting.start",
              "event_config": {
                "time_offset": 0
              }
            },
            "output_schema": {
              "type": "object",
              "properties": {
                "meeting": {
                  "description": "the meeting object includes meeting info like meeting topic, meeting host etc",
                  "type": "meeting",
                  "required": true
                }
              }
            },
            "expanded": false,
            "children": {
              "nodes": [],
              "edges": []
            }
          },
          {
            "node_id": "zm.aic.agent.deep_reasoning",
            "reference_id": "RADGGY",
            "node_type": "deep_reasoning",
            "node_name": "deep_reasoning",
            "node_alias": "Send Join Link to External Contacts",
            "node_desc": "",
            "input_map": {
              "name": "External Meeting Notifier",
              "description": "This agent automatically identifies external contacts when a meeting starts and sends them a polished, email-ready notification containing the meeting join link. It retrieves meeting details from the meeting_started_trigger context, distinguishes internal and external participants based on email domains, and sends the join link only to external attendees. If no external contacts or join link are available, it returns a clear skipped status with the reason.",
              "system_prompt": "You are a precise and courteous meeting automation assistant. When a meeting starts, you retrieve meeting details and attendee information from the provided trigger context. You identify external contacts by comparing attendee email domains with the host's domain, excluding internal participants. You then generate a professional, email-ready message that includes a clear subject, a brief greeting, the meeting topic if available, the join link, and a short note that the meeting has started. You send this message only to external contacts using the selected email tool. If no external contacts or join link are available, you return a skipped status with a clear reason. All generated messages must be polished, human-readable, and ready to send without further editing.\n\nUSER-FACING TEXT FORMATTING CONTRACT:\n- Any model-generated text that a workflow shows or sends to a person must be\n  ready to read, not a raw dump. This includes chat messages, channel posts,\n  email subject/body content, notifications, summaries, reports, document text,\n  and action/comment descriptions.\n- Use clear structure: concise opening line, short paragraphs, bullet or\n  numbered lists for multiple items, section labels when helpful, and blank\n  lines between sections. Include owners, dates, decisions, risks, and action\n  items as separate readable lines when present.\n- Match the user's language and requested tone. Preserve facts and meaning.\n  Do not output raw JSON, dense comma-separated blobs, markdown tables, or\n  unbroken paragraphs unless the user explicitly asks for that format.\n- For email bodies, generate clean email-ready content: useful subject text,\n  greeting/sign-off when appropriate, readable paragraphs, and lists for\n  action items. If the selected email tool expects HTML, provide semantic HTML;\n  otherwise provide well-structured plain text or markdown accepted by the tool.\n- When the task both generates content and sends/posts/shares/saves it through\n  a tool, complete that whole flow in this node: generate the readable content,\n  then call the selected destination tool (for example Gmail send email) with\n  that content and the requested recipients/destination.\n\nZOOM CHAT/CHANNEL FORMATTER REQUIREMENT:\n- When this node drafts, summarizes, or otherwise generates content that will\n  be sent to Zoom Chat, Zoom Team Chat, a Zoom Channel, or a Zoom Chat DM, it\n  MUST use the `zoom_chat_formatter` skill before outputting or sending the\n  message.\n- Required sequence: first generate the message content, then apply\n  `zoom_chat_formatter` to beautify it for Zoom Chat syntax/readability, then\n  output only the beautified content for the downstream Zoom Chat/Channel send\n  step. Do not send the raw model-generated draft to Zoom Chat/Channel.",
              "task_prompt": "When the meeting starts, identify the external contacts from the current trigger meeting using {{EFTEST.meeting}}. Use the meeting attendee list and host/domain context to distinguish external contacts from internal participants where possible. Send those external contacts the meeting join link from the current meeting. The message should be polished and email-ready: include a clear subject, a brief greeting, the meeting topic if available, the join link, and a short note that the meeting has started. Do not send to internal participants. If no external contact email addresses or no join link are available, return a clear skipped status with the reason instead of inventing recipients or links.",
              "context": {},
              "tools": [
                {
                  "name": "zm.system.resource.unified_search",
                  "description": "Zoom System Utilities - Resource search data node. This data node retrieves relevant Zoom resources such as meetings, team chats, and Zoom Docs using semantic search or precise queries, and outputs the matched content for downstream workflow steps. Use cases include finding past meeting transcripts for summarization, retrieving chat history for context-aware automation, and locating Zoom Docs to extract or analyze specific information.",
                  "app_key": ""
                },
                {
                  "name": "zm.aic.tool.resolve_entity",
                  "description": "Zoom AI Companion - Resolve Entity tool node. This tool converts human-readable names such as chat channels, user names, or meeting topics into their corresponding system IDs so they can be used in downstream workflow steps. Use cases include resolving a channel name before sending a message, finding a user ID to assign a task or send a notification, and locating a meeting ID within a specific time window for follow-up actions.",
                  "app_key": ""
                },
                {
                  "name": "zm.aic.tool.search_web",
                  "description": "Zoom AI Companion - Web search action. This tool node performs real-time web searches to retrieve up-to-date information, optionally restricting results to specific domains for targeted research. Use cases include answering open-domain questions with current data, gathering competitive or industry insights from trusted websites, and fact-checking or enriching responses with the latest online information.",
                  "app_key": "web_search"
                },
                {
                  "name": "zm.connector_mcp.zm_gmail.gmail_send_email",
                  "description": "Gmail - Sends an email via Gmail API using the authenticated user's Google profile display name. This is a tool node.",
                  "app_key": "zm_gmail"
                }
              ],
              "skills": [
                {
                  "name": "zoom_chat_formatter",
                  "description": "Beautifies model-generated content for Zoom Chat/Channel by converting unsupported markdown syntax into Zoom-compatible equivalents, adding emoji-based section headers, extracting action items, bolding summary sentences, converting implicit enumerations into bullet lists, and splitting dense paragraphs; accepts any plain text or markdown content; use when the user asks to format, reformat, send, or prepare content for Zoom Chat or a Zoom Channel.",
                  "skill_type": "static",
                  "skill_path": "zoom_chat_formatter",
                  "required_tools": []
                }
              ],
              "model": "gpt-5-chat-latest",
              "max_tokens": 200000,
              "max_rounds": 10,
              "expected_format": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Sent, skipped, or failed."
                  },
                  "sent_count": {
                    "type": "integer",
                    "description": "Number of external contacts who were sent the join link."
                  },
                  "recipients": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "External contact email addresses that received the join link."
                  },
                  "message_subject": {
                    "type": "string",
                    "description": "The subject used for the outgoing message."
                  },
                  "summary": {
                    "type": "string",
                    "description": "A concise human-readable result summary."
                  }
                },
                "required": [
                  "status",
                  "sent_count",
                  "recipients",
                  "summary"
                ]
              },
              "outputs": [
                {
                  "name": "status",
                  "type": "string",
                  "description": "Sent, skipped, or failed."
                },
                {
                  "name": "sent_count",
                  "type": "integer",
                  "description": "Number of external contacts who were sent the join link."
                },
                {
                  "name": "recipients",
                  "type": "array",
                  "description": "External contact email addresses that received the join link."
                },
                {
                  "name": "message_subject",
                  "type": "string",
                  "description": "The subject used for the outgoing message."
                },
                {
                  "name": "summary",
                  "type": "string",
                  "description": "A concise human-readable result summary."
                }
              ],
              "output_spec_description": [
                {
                  "name": "status",
                  "type": "string",
                  "description": "Sent, skipped, or failed."
                },
                {
                  "name": "sent_count",
                  "type": "integer",
                  "description": "Number of external contacts who were sent the join link."
                },
                {
                  "name": "recipients",
                  "type": "array",
                  "description": "External contact email addresses that received the join link."
                },
                {
                  "name": "message_subject",
                  "type": "string",
                  "description": "The subject used for the outgoing message."
                },
                {
                  "name": "summary",
                  "type": "string",
                  "description": "A concise human-readable result summary."
                }
              ],
              "knowledge": {
                "web_urls": ""
              },
              "max_steps": 20
            },
            "config": {
              "execution_mode": "workflow"
            },
            "output_schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "description": "Sent, skipped, or failed."
                },
                "sent_count": {
                  "type": "integer",
                  "description": "Number of external contacts who were sent the join link."
                },
                "recipients": {
                  "type": "array",
                  "description": "External contact email addresses that received the join link."
                },
                "message_subject": {
                  "type": "string",
                  "description": "The subject used for the outgoing message."
                },
                "summary": {
                  "type": "string",
                  "description": "A concise human-readable result summary."
                }
              }
            },
            "expanded": false,
            "children": {
              "nodes": [],
              "edges": []
            }
          }
        ],
        "edges": [
          {
            "source_node": "EFTEST",
            "target_node": "RADGGY"
          }
        ]
      }
    ]
  }
}

Option 3 — Use participant.invite.room_system_callout for SIP/H.323 devices

If your external participant joins via a room system, the participant.invite.room_system_callout method supports external SIP/H.323 addresses and does not enforce the same-account restriction.