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:
- Creating a new chat in the middle of sending a message adds some delay to the delivery of the message.
- 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.