> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vivi.bot/llms.txt
> Use this file to discover all available pages before exploring further.

# Trigger Conversations API

> Start outbound calls, SMS messages, and Microsoft Teams messages from your systems.

The Trigger Conversations API starts outbound conversations through an existing VIVI channel. Use it to send appointment reminders, operational alerts, status updates, or other event-driven messages from your backend systems.

The endpoint supports **Twilio Phone**, **SIP Trunk**, **Twilio SMS**, and **Microsoft Teams & Copilot** channels.

<CardGroup cols={2}>
  <Card title="Endpoint" icon="code">
    `POST https://api.vivi.bot/channelsApi/{channelId}/conversations/trigger`
  </Card>

  <Card title="API reference" icon="book-open" href="https://api.vivi.bot/redoc#tag/Trigger/operation/trigger_conversations_channelsApi__channelId__conversations_trigger_post">
    View the complete request and response schema in ReDoc.
  </Card>
</CardGroup>

## Before you start

You need:

* A configured Twilio Phone, SIP Trunk, Twilio SMS, or Microsoft Teams & Copilot channel.
* The channel ID, available from the channel details page in VIVI.
* An active account or workspace API credential and its `clientId`, `clientSecret`, `audience`, and `tokenUrl`.

See [API Credentials](https://docs.vivi.bot/documentation/accounts-billing/authentication/api-credentials) for instructions on creating credentials and exchanging them for an access token.

<Warning>
  The endpoint accepts only machine-to-machine access tokens. An interactive user token or an API channel's `vivi-api-key` will not work.
</Warning>

## Request an access token

Exchange your API credentials for an access token. Cache and reuse the token until it is close to expiring.

```bash cURL theme={null}
curl --request POST "YOUR_TOKEN_URL" \
  --header "Content-Type: application/json" \
  --data '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "audience": "YOUR_AUDIENCE",
    "grant_type": "client_credentials"
  }'
```

Send the returned token on trigger requests as `Authorization: Bearer YOUR_ACCESS_TOKEN`.

## Request body

<ParamField path="messageTemplate" type="string" required>
  The message or voice prompt to send. Maximum 4,000 characters. Use `{variable}` placeholders to personalize the message for each recipient.
</ParamField>

<ParamField path="recipients" type="array" required>
  One or more recipients. The default maximum is 100 recipients per request. Every recipient contains a channel-specific `target` and optional `metadata` values.
</ParamField>

<ParamField path="recipients[].target" type="string" required>
  The destination. Its accepted format depends on the channel type. Maximum 256 characters.
</ParamField>

<ParamField path="recipients[].metadata" type="object">
  String values used to replace placeholders in `messageTemplate`. Every recipient must provide every variable used by the template. Maximum encoded size: 4 KB per recipient.
</ParamField>

<ParamField path="metadata" type="object">
  Optional string metadata for your own request context. Maximum encoded size: 8 KB.
</ParamField>

The API validates the complete batch before dispatching anything. Duplicate targets, invalid targets, or missing template variables reject the entire request.

## Channel examples

Replace `YOUR_CHANNEL_ID` and `YOUR_ACCESS_TOKEN` in the examples below. The channel ID must belong to the same account as the API credential.

<Tabs>
  <Tab title="Twilio Phone">
    Use an E.164 phone number, including `+` and the country code. VIVI starts a voice call from the first phone number configured on the channel.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
        --url "https://api.vivi.bot/channelsApi/YOUR_CHANNEL_ID/conversations/trigger" \
        --header "Authorization: Bearer YOUR_ACCESS_TOKEN" \
        --header "Content-Type: application/json" \
        --header "Idempotency-Key: 2ccaca2e-08d9-42ec-bfd5-eaa92f43ed22" \
        --data '{
          "messageTemplate": "Hello {firstName}. Your appointment is {appointmentTime}.",
          "recipients": [
            {
              "target": "+14155550123",
              "metadata": {
                "firstName": "Avery",
                "appointmentTime": "Tuesday at 10:30 AM"
              }
            }
          ],
          "metadata": {"source": "scheduling-system"}
        }'
      ```

      ```python Python theme={null}
      import requests

      channel_id = "YOUR_CHANNEL_ID"
      access_token = "YOUR_ACCESS_TOKEN"

      response = requests.post(
          f"https://api.vivi.bot/channelsApi/{channel_id}/conversations/trigger",
          headers={
              "Authorization": f"Bearer {access_token}",
              "Idempotency-Key": "2ccaca2e-08d9-42ec-bfd5-eaa92f43ed22",
          },
          json={
              "messageTemplate": "Hello {firstName}. Your appointment is {appointmentTime}.",
              "recipients": [
                  {
                      "target": "+14155550123",
                      "metadata": {
                          "firstName": "Avery",
                          "appointmentTime": "Tuesday at 10:30 AM",
                      },
                  }
              ],
              "metadata": {"source": "scheduling-system"},
          },
          timeout=30,
      )
      response.raise_for_status()
      print(response.json())
      ```

      ```javascript Node.js theme={null}
      const channelId = "YOUR_CHANNEL_ID";
      const accessToken = "YOUR_ACCESS_TOKEN";

      const response = await fetch(
        `https://api.vivi.bot/channelsApi/${channelId}/conversations/trigger`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${accessToken}`,
            "Content-Type": "application/json",
            "Idempotency-Key": "2ccaca2e-08d9-42ec-bfd5-eaa92f43ed22",
          },
          body: JSON.stringify({
            messageTemplate: "Hello {firstName}. Your appointment is {appointmentTime}.",
            recipients: [
              {
                target: "+14155550123",
                metadata: {
                  firstName: "Avery",
                  appointmentTime: "Tuesday at 10:30 AM",
                },
              },
            ],
            metadata: { source: "scheduling-system" },
          }),
        },
      );

      if (!response.ok) throw new Error(`Trigger failed: ${response.status}`);
      console.log(await response.json());
      ```
    </CodeGroup>
  </Tab>

  <Tab title="SIP Trunk">
    Use an E.164 phone number, a PBX extension such as `6000`, or a SIP user part containing letters, digits, periods, underscores, or hyphens. VIVI starts a voice call through the configured trunk.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
        --url "https://api.vivi.bot/channelsApi/YOUR_CHANNEL_ID/conversations/trigger" \
        --header "Authorization: Bearer YOUR_ACCESS_TOKEN" \
        --header "Content-Type: application/json" \
        --header "Idempotency-Key: ed700603-2cb2-4118-883e-3f56619b000f" \
        --data '{
          "messageTemplate": "Hello {firstName}. Your appointment is {appointmentTime}.",
          "recipients": [
            {
              "target": "6000",
              "metadata": {
                "firstName": "Avery",
                "appointmentTime": "Tuesday at 10:30 AM"
              }
            }
          ],
          "metadata": {"source": "scheduling-system"}
        }'
      ```

      ```python Python theme={null}
      import requests

      channel_id = "YOUR_CHANNEL_ID"
      access_token = "YOUR_ACCESS_TOKEN"

      response = requests.post(
          f"https://api.vivi.bot/channelsApi/{channel_id}/conversations/trigger",
          headers={
              "Authorization": f"Bearer {access_token}",
              "Idempotency-Key": "ed700603-2cb2-4118-883e-3f56619b000f",
          },
          json={
              "messageTemplate": "Hello {firstName}. Your appointment is {appointmentTime}.",
              "recipients": [
                  {
                      "target": "6000",
                      "metadata": {
                          "firstName": "Avery",
                          "appointmentTime": "Tuesday at 10:30 AM",
                      },
                  }
              ],
              "metadata": {"source": "scheduling-system"},
          },
          timeout=30,
      )
      response.raise_for_status()
      print(response.json())
      ```

      ```javascript Node.js theme={null}
      const channelId = "YOUR_CHANNEL_ID";
      const accessToken = "YOUR_ACCESS_TOKEN";

      const response = await fetch(
        `https://api.vivi.bot/channelsApi/${channelId}/conversations/trigger`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${accessToken}`,
            "Content-Type": "application/json",
            "Idempotency-Key": "ed700603-2cb2-4118-883e-3f56619b000f",
          },
          body: JSON.stringify({
            messageTemplate: "Hello {firstName}. Your appointment is {appointmentTime}.",
            recipients: [
              {
                target: "6000",
                metadata: {
                  firstName: "Avery",
                  appointmentTime: "Tuesday at 10:30 AM",
                },
              },
            ],
            metadata: { source: "scheduling-system" },
          }),
        },
      );

      if (!response.ok) throw new Error(`Trigger failed: ${response.status}`);
      console.log(await response.json());
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Twilio SMS">
    Use an E.164 phone number, including `+` and the country code. VIVI sends the message from the number configured on the channel. Consent, opt-out, and A2P rules still apply.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
        --url "https://api.vivi.bot/channelsApi/YOUR_CHANNEL_ID/conversations/trigger" \
        --header "Authorization: Bearer YOUR_ACCESS_TOKEN" \
        --header "Content-Type: application/json" \
        --header "Idempotency-Key: b8ee9526-8769-418b-b2e1-c1171b95d2d1" \
        --data '{
          "messageTemplate": "Hi {firstName}, order {orderNumber} is ready for pickup.",
          "recipients": [
            {
              "target": "+14155550123",
              "metadata": {
                "firstName": "Avery",
                "orderNumber": "VIVI-1042"
              }
            }
          ],
          "metadata": {"source": "order-management"}
        }'
      ```

      ```python Python theme={null}
      import requests

      channel_id = "YOUR_CHANNEL_ID"
      access_token = "YOUR_ACCESS_TOKEN"

      response = requests.post(
          f"https://api.vivi.bot/channelsApi/{channel_id}/conversations/trigger",
          headers={
              "Authorization": f"Bearer {access_token}",
              "Idempotency-Key": "b8ee9526-8769-418b-b2e1-c1171b95d2d1",
          },
          json={
              "messageTemplate": "Hi {firstName}, order {orderNumber} is ready for pickup.",
              "recipients": [
                  {
                      "target": "+14155550123",
                      "metadata": {
                          "firstName": "Avery",
                          "orderNumber": "VIVI-1042",
                      },
                  }
              ],
              "metadata": {"source": "order-management"},
          },
          timeout=30,
      )
      response.raise_for_status()
      print(response.json())
      ```

      ```javascript Node.js theme={null}
      const channelId = "YOUR_CHANNEL_ID";
      const accessToken = "YOUR_ACCESS_TOKEN";

      const response = await fetch(
        `https://api.vivi.bot/channelsApi/${channelId}/conversations/trigger`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${accessToken}`,
            "Content-Type": "application/json",
            "Idempotency-Key": "b8ee9526-8769-418b-b2e1-c1171b95d2d1",
          },
          body: JSON.stringify({
            messageTemplate: "Hi {firstName}, order {orderNumber} is ready for pickup.",
            recipients: [
              {
                target: "+14155550123",
                metadata: {
                  firstName: "Avery",
                  orderNumber: "VIVI-1042",
                },
              },
            ],
            metadata: { source: "order-management" },
          }),
        },
      );

      if (!response.ok) throw new Error(`Trigger failed: ${response.status}`);
      console.log(await response.json());
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Microsoft Teams">
    Use the canonical lowercase UUID of a registered Teams message target. Find target IDs in the **Proactive Teams messages** section of the channel. A user principal name or Microsoft Entra object ID is not accepted.

    The VIVI app must already be part of the destination conversation. See [Microsoft Teams & Copilot](https://docs.vivi.bot/documentation/core-concepts/channels/ms-teams#proactive-messaging) for target registration instructions.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
        --url "https://api.vivi.bot/channelsApi/YOUR_CHANNEL_ID/conversations/trigger" \
        --header "Authorization: Bearer YOUR_ACCESS_TOKEN" \
        --header "Content-Type: application/json" \
        --header "Idempotency-Key: 9cb879a8-b52a-42ca-be83-f35c7b7bfeb5" \
        --data '{
          "messageTemplate": "Hi {firstName}, support case {caseNumber} was updated.",
          "recipients": [
            {
              "target": "8f4c8f7d-8f40-4ea4-a4f0-891adb8619fd",
              "metadata": {
                "firstName": "Avery",
                "caseNumber": "CS-4815"
              }
            }
          ],
          "metadata": {"source": "support-system"}
        }'
      ```

      ```python Python theme={null}
      import requests

      channel_id = "YOUR_CHANNEL_ID"
      access_token = "YOUR_ACCESS_TOKEN"

      response = requests.post(
          f"https://api.vivi.bot/channelsApi/{channel_id}/conversations/trigger",
          headers={
              "Authorization": f"Bearer {access_token}",
              "Idempotency-Key": "9cb879a8-b52a-42ca-be83-f35c7b7bfeb5",
          },
          json={
              "messageTemplate": "Hi {firstName}, support case {caseNumber} was updated.",
              "recipients": [
                  {
                      "target": "8f4c8f7d-8f40-4ea4-a4f0-891adb8619fd",
                      "metadata": {
                          "firstName": "Avery",
                          "caseNumber": "CS-4815",
                      },
                  }
              ],
              "metadata": {"source": "support-system"},
          },
          timeout=30,
      )
      response.raise_for_status()
      print(response.json())
      ```

      ```javascript Node.js theme={null}
      const channelId = "YOUR_CHANNEL_ID";
      const accessToken = "YOUR_ACCESS_TOKEN";

      const response = await fetch(
        `https://api.vivi.bot/channelsApi/${channelId}/conversations/trigger`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${accessToken}`,
            "Content-Type": "application/json",
            "Idempotency-Key": "9cb879a8-b52a-42ca-be83-f35c7b7bfeb5",
          },
          body: JSON.stringify({
            messageTemplate: "Hi {firstName}, support case {caseNumber} was updated.",
            recipients: [
              {
                target: "8f4c8f7d-8f40-4ea4-a4f0-891adb8619fd",
                metadata: {
                  firstName: "Avery",
                  caseNumber: "CS-4815",
                },
              },
            ],
            metadata: { source: "support-system" },
          }),
        },
      );

      if (!response.ok) throw new Error(`Trigger failed: ${response.status}`);
      console.log(await response.json());
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Response

The endpoint returns `201 Created` after processing the batch. A successful HTTP response can contain both successful and failed recipients, so inspect every item in `results`. Results remain in the same order as the request.

```json theme={null}
{
  "results": [
    {
      "index": 0,
      "threadId": "a2ed4f17-c880-4c56-bf1b-1547d8b129b7",
      "recipient": "+14155550123",
      "channelType": "twilio_sms",
      "status": "initiating",
      "agentResponse": null,
      "error": null
    }
  ]
}
```

| Field           | Description                                                                  |
| --------------- | ---------------------------------------------------------------------------- |
| `index`         | The recipient's zero-based position in the request.                          |
| `threadId`      | The VIVI conversation thread ID, or `null` if a thread could not be created. |
| `recipient`     | The original target.                                                         |
| `channelType`   | The channel type used for the recipient.                                     |
| `status`        | `initiating`, `completed`, or `failed`.                                      |
| `agentResponse` | Agent response text for a synchronous completion, otherwise `null`.          |
| `error`         | A code, message, and `retriable` flag when the recipient failed.             |

Voice and SMS dispatches normally return `initiating`. Microsoft Teams returns `completed` after provider confirmation. Recipient-specific failures return `failed` with an `error` object.

## Retries, idempotency, and rate limits

<AccordionGroup>
  <Accordion title="Partial failures">
    A `201` response means that the batch was processed, not that every recipient succeeded. Retry only results where `error.retriable` is `true`, and send a new request containing only those recipients. Generate a new `Idempotency-Key` for the retry request.
  </Accordion>

  <Accordion title="Idempotency-Key">
    The optional `Idempotency-Key` header prevents a concurrent duplicate from dispatching twice while the first request is processing. The reservation is released after processing completes, so reusing the key later executes the request again and can create a duplicate.
  </Accordion>

  <Accordion title="Rate limit">
    Each workspace accepts 60 trigger requests per rolling 60-second window. A `429` response includes a `Retry-After` header with the number of seconds to wait.
  </Accordion>
</AccordionGroup>

## Request errors

| Status | Meaning                                                                                     |
| ------ | ------------------------------------------------------------------------------------------- |
| `400`  | The channel type, target, template variables, or recipient list failed semantic validation. |
| `401`  | The Bearer token is missing, invalid, or is not a machine-to-machine token.                 |
| `403`  | The service client is inactive or does not own the channel's account.                       |
| `404`  | The channel was not found in the service client's account.                                  |
| `409`  | The idempotency key is in progress or was reused concurrently with a different payload.     |
| `422`  | The JSON body is missing required fields or violates field types or size limits.            |
| `429`  | The workspace exceeded the trigger rate limit. Honor the `Retry-After` header.              |

Error responses include a stable `code`, a human-readable `message`, a `retriable` flag, and a `request_id` for support and log correlation.
