Reference
Messages
🚫

Internal use only!

⚠️

Although the Management API exposes endpoints for messages, it is recommended you use the equivalent endpoints on the Messaging API as the Messaging API is optimized for messaging and has a real-time connection with the ProteusAI SDK, making message streaming possible.

A message is a piece of content sent by a sender to one or more recipients in a chat. The sender can be a user, agent, or app integration. The recipient can be any entity that is a member of the same organization as the sender, or a public agent.

A message can be of various types: plain text, markdown, HTML, uploaded files, a remote procedure/function call, and even UI elements like forms, cards, and carousels. The type of a message will determine how its content will be rendered.

⚠️

When building plugins for third-party apps, developers should be aware of what types of messages are supported by the apps, and how to handle unsupported types. One recommendation is that the developer generate an image rendering of the unsupported message and forward the image to the third-party app. Another recommendation is for the developer to forward, to the third-party app, a link that would take a user to a web page where that unsupported message is properly rendered.

Send Message

POST /api/messages

Both original messages and responses to those messages can be sent via this API. We will examine various use cases of this API.

To Agents

Here, a user sends a message to an agent.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer user-3a4d80a9c3cca7ffd1bc341f.ede04d00e4b168bed35d9fcde8c2f92f.00d7fecf89201691f94b01e5b294da65.e7ea4c191b2c9a059ce5658deb7349c26a931ed8b7a04bf0" \
    -d '{
            "content": "Hello, how are you?",
            "recipients": [
                { "id": "6819a0e484df0fc342cea506", "type": "AGENT" }
            ]
        }'

Do not be alarmed if the response does not have the expected values for fields like attachments, sender, recipients, and a few more. These fields are often times updated after the request has returned and after the relevant events have been dispatched.

When a message is sent in this format, without explicitly specifying a chatId or chatKey, a chat is automatically created for the message. This is why the response object contains a chatId field. This also implies that the events associated with creating a new chat and adding participants to it (like AGENT_CHAT_JOINED and CHAT_PARTICIPANT_JOINED) will be dispatched.

Then, the AGENT_MESSAGE_RECEIVED event is dispatched to the webhooks of the plugins attached to the agent to which the message was sent.

When adding event handlers to an agent, install the plugins with the relevant events so that they are able to be notified when those events happen. Two such events are AGENT_CHAT_JOINED and AGENT_MESSAGE_RECEIVED.

With responseUrl

Note that the HTTP response you get from the /api/messages endpoint is not the agent's/recipient's reply to your message. To get a reply back, you need to include a responseUrl field in your message. This URL should point to an endpoint on a server under your control. When a reply to your message is ready, the WEBHOOK_MESSAGE_RECEIVED event will be dispatched to the responseUrl.

When adding event handlers to an agent, if you wish for the plugins to be able to generate replies on behalf of your agent, install the plugins with the scopes AGENT_CHAT_WRITE and AGENT_MESSAGE_WRITE.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer user-3a4d80a9c3cca7ffd1bc341f.ede04d00e4b168bed35d9fcde8c2f92f.00d7fecf89201691f94b01e5b294da65.e7ea4c191b2c9a059ce5658deb7349c26a931ed8b7a04bf0" \
    -d '{
            "content": "Hello, how are you?",
            "recipients": [
                { "id": "6819a0e484df0fc342cea506", "type": "AGENT" }
            ],
            "responseUrl": "https://myplugins.com/apps/proteusai-to-slack/webhook"
        }'

The responseUrl, https://myplugins.com/apps/proteusai-to-slack/webhook in this case, will receive a WEBHOOK_MESSAGE_RECEIVED event containing the generated reply.

{
  "name": "WEBHOOK_MESSAGE_RECEIVED",
  "message": {
    "id": "683372cb10360b306da3bf06",
    "content": "Hello! I'm just a program, but I'm here and ready to help you. How can I assist you today?",
    "contentDelta": null,
    "chatId": "683372c110360b306da3bf00",
    "chatKey": null,
    "createdAt": "2025-05-25T19:43:07.467Z",
    "instructions": [],
    "isStreaming": false,
    "notes": [],
    "processed": false,
    "respondedToId": "683372c410360b306da3bf03",
    "responseUrl": "https://myplugins.com/apps/proteusai-to-slack/webhook",
    "senderId": null,
    "state": {},
    "streamId": null,
    "streamResponse": false,
    "type": "TEXT",
    "updatedAt": "2025-05-25T19:43:07.467Z"
  }
}

Recall that the HTTP response you get from the /api/messages endpoint is not the recipient's reply to your message. The recipient's reply is in the WEBHOOK_MESSAGE_RECEIVED event dispatched to the responseUrl.

From App Integrations

When an app integration sends a message, there's typically no need to specify recipients because the agents associated with the app integration are automatically chosen as the recipients. Likewise, there's no need to specify responseUrl because the webhook of the plugin installed on the app integration is automatically set as the value of the responseUrl.

This is a good place to buttress the importance of scopes. The plugin instance on the app integration was installed without the APP_INTEGRATION_CHAT_WRITE scope. It was, however, installed with the APP_INTEGRATION_MESSAGE_WRITE scope. This means the plugin instance on the app integration can send messages on behalf of the app integration, but cannot create new chats. This means sending a message without chatId or chatKey would fail because a new chat is always created for such messages. See the response below.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer Bearer inst-6b98ba56281fcd753c5862ee.59e9573b035f57a097b2e1aba6f9e1f2.7947a0c8982bce94ebe136bcdff61c85.8f48642ffc825e55049a7a3c5bb7abfa504f4241529feb9e" \
    -d '{
            "content": "Hello, how are you?"
        }'

After reinstalling the plugin instance on the app integration with both the APP_INTEGRATION_CHAT_WRITE and APP_INTEGRATION_MESSAGE_WRITE scopes, it becomes possible to send messages with just the content field.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer Bearer inst-6b98ba56281fcd753c5862ee.59e9573b035f57a097b2e1aba6f9e1f2.7947a0c8982bce94ebe136bcdff61c85.8f48642ffc825e55049a7a3c5bb7abfa504f4241529feb9e" \
    -d '{
            "content": "Hello, how are you?"
        }'

When this message is sent, without explicitly specifying a chatId or chatKey, a chat is automatically created for the message. This is why the response object contains a chatId field. This also implies that the events associated with creating a new chat and adding participants to it (like APP_INTEGRATION_CHAT_JOINED and CHAT_PARTICIPANT_JOINED) will be dispatched.

The webhookUrl, https://myplugins.com/app-integration-apps/slack/webhook in this case, of the plugin installed on the app integration will receive an APP_INTEGRATION_MESSAGE_RECEIVED event containing the generated reply.

{
  "name": "APP_INTEGRATION_MESSAGE_RECEIVED",
  "appIntegration": {
    "id": "6825a9f67306f72d65708b74",
    "agentIds": [ "6819a0e484df0fc342cea506" ],
    "createdAt": "2025-05-15T08:46:46.803Z",
    "description": "This app integration allows Slack users to communicate with ProteusAI agents",
    "name": "Slack<>ProteusAI Integration",
    "orgId": "6816330b6f46b5afc080fbf7",
    "updatedAt": "2025-05-16T13:54:25.582Z"
  },
  "message": {
    "id": "6833925110360b306da3bf11",
    "content": "Hello! I'm just a computer program, so I don't have feelings, but I'm here and ready to assist you. How can I help you today?",
    "contentDelta": null,
    "chatId": "6833924710360b306da3bf0b",
    "chatKey": null,
    "createdAt": "2025-05-25T21:57:37.247Z",
    "instructions": [],
    "isStreaming": false,
    "notes": [],
    "processed": false,
    "respondedToId": "6833924a10360b306da3bf0e",
    "responseUrl": "https://myplugins.com/app-integration-apps/slack/webhook",
    "senderId": null,
    "state": {},
    "streamId": null,
    "streamResponse": false,
    "type": "TEXT",
    "updatedAt": "2025-05-25T21:57:37.247Z"
  },
  "pluginInstance": {
    "id": "6833918910360b306da3bf09",
    "agentId": null,
    "appIntegrationId": "6825a9f67306f72d65708b74",
    "createdAt": "2025-05-25T21:54:17.745Z",
    "entityId": "6825a9f67306f72d65708b74",
    "entityType": "APP_INTEGRATION",
    "eventDispatchCondition": null,
    "eventDispatchMode": "ASYNC",
    "events": [
      "APP_INTEGRATION_CHAT_JOINED",
      "APP_INTEGRATION_MESSAGE_RECEIVED",
      "CHAT_PARTICIPANT_JOINED"
    ],
    "inputs": null,
    "name": "Slack App <> Slack<>ProteusAI Integration (#6825a9f67306f72d65708b74)",
    "orgId": "6816330b6f46b5afc080fbf7",
    "pluginId": "6825f0a6d6191933ffd08112",
    "repositoryId": null,
    "scopes": [ "APP_INTEGRATION_MESSAGE_WRITE", "APP_INTEGRATION_CHAT_WRITE" ],
    "updatedAt": "2025-05-25T21:54:17.745Z"
  }
}

Recall that when an app integration sends a message, there's typically no need to specify recipients because the agents associated with the app integration are automatically chosen as the recipients. Likewise, there's no need to specify responseUrl because the webhook of the plugin installed on the app integration is automatically set as the value of the responseUrl. If, however, recipients and responseUrl are specified in the request body, then then specified recipients are merged with the agents associated with the app integration to get the real list of recipients while the specified responseUrl and the webhook of the plugin associated with the app integration get the WEBHOOK_MESSAGE_RECEIVED and the APP_INTEGRATION_MESSAGE_RECEIVED events, respectively.

The webhook of the plugin installed on the app integration may also get the WEBHOOK_MESSAGE_RECEIVED event (in addition to the APP_INTEGRATION_MESSAGE_RECEIVED event). This is because, if no responseUrl is provided, ProteusAI may set responseUrl to the webhook of the plugin installed on the app integration. This implementation may change.

What should you do?
In the webhook of the plugin, only listen for APP_INTEGRATION_MESSAGE_RECEIVED event as it is guaranteed to be delivered.

With chatId

Every time a message is sent without chatId and chatKey a new chat is created to house that message. This is not ideal for 2 major reasons:

  1. Creating a new chat in the middle of sending a message adds some delay to the delivery of the message.
  2. Creating a new chat for every message means a thread of conversation consisting of multiple messages is not possible.

For high-quality conversations, especially with AI agents, it's highly recommended that you have your messages in a chat. This allows the recipient of a message to be able to look into the chat for previous messages in order to gain better context on what is being spoken about in the current message.

To specify the chat a message belongs to, provide a chatId field in the request body.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer Bearer inst-6b98ba56281fcd753c5862ee.59e9573b035f57a097b2e1aba6f9e1f2.7947a0c8982bce94ebe136bcdff61c85.8f48642ffc825e55049a7a3c5bb7abfa504f4241529feb9e" \
    -d '{
            "content": "Hello, how are you?",
            "chatId": "6833924710360b306da3bf0b"
        }'

This time, no new chat will be created, and events like AGENT_CHAT_JOINED, APP_INTEGRATION_CHAT_JOINED, and CHAT_PARTICIPANT_JOINED will not be dispatched.

🚫

Sending a message with a wrong chatId or with a chatId that references a chat you are not a participant of will fail with an error response.

⚠️

A message sent to a recipient who is not part of the specified chat will not be delivered.

If the recipients list consists of both members of the chat and non-members, only the members of the chat will have the message delivered to them.

If chatId is specified, there is no need to provide recipients as all participants of the chat (other than the sender) are automatically the recipients of the message. You can, however, specify a subset of the participants of the chat as the recipients of the message by explicitly listing them in the recipients field.

With chatKey

It may not always to possible to know the chat in which a message belongs. For instance, say you are building an integration between Slack and ProteusAI where Slack users can chat with ProteusAI agents. When a message comes in from Slack, it may not be possible to determine the right ProteusAI chatId for that message. In these situations, it is recommended you use a chatKey. The chatKey can be any text, but you may try to make it unique to the sender of the message. With the Slack example, you could make the chatKey equal to the Slack user ID of the user who sent the message on Slack or a combination of the user ID and channel ID so that messages from that user in different channels have different values for chatKey.

The first time you send a message with a chatKey, a new chat is created.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer Bearer inst-6b98ba56281fcd753c5862ee.59e9573b035f57a097b2e1aba6f9e1f2.7947a0c8982bce94ebe136bcdff61c85.8f48642ffc825e55049a7a3c5bb7abfa504f4241529feb9e" \
    -d '{
            "content": "Hello, how are you?",
            "chatKey": "chat_key_1"
        }'

The next time you send a message with the same chatKey, ProteusAI will search for a chat with the specified key in which the message sender is a member. Notice that the chatId in the response below is the same as the chatId in the response above, showing that no new chat was created.

curl https://management-api.useproteus.ai/api/messages \
    -X POST \
    -H "Authorization: Bearer Bearer inst-6b98ba56281fcd753c5862ee.59e9573b035f57a097b2e1aba6f9e1f2.7947a0c8982bce94ebe136bcdff61c85.8f48642ffc825e55049a7a3c5bb7abfa504f4241529feb9e" \
    -d '{
            "content": "Hello, how are you yet again?",
            "chatKey": "chat_key_1"
        }'

If both chatId and chatKey are specified, only chatId is considered.


© 2025 ProteusAI. All rights reserved