POST /conversations/messaging/send failing with 400 on canned response payload

Could someone clarify why the Conversations API rejects this JSON body with a 400 Bad Request? Using application/json and valid JWT.

{
 "text": "{{canned_response_text}}"
}

What is the correct payload structure for sending a canned response text via the Messaging API?

You need to stop sending the raw text string inside the message body. The Messaging API endpoint POST /api/v2/conversations/messaging/{conversationId}/messages expects a structured ConversationMessage object, not a simple key-value pair for the text content. When you pass {"text": "..."}, the schema validator fails because the required to and from fields are missing, and the text itself isn’t wrapped in a content object.

Here is the correct JSON payload structure. You must include the to (the other participant’s address) and from (the agent’s address) fields. Also, ensure your JWT has the conversations:messaging:write scope.

{
 "to": {
 "id": "guest-uuid-from-conversation",
 "name": "Guest Name"
 },
 "from": {
 "id": "agent-uuid",
 "name": "Agent Name"
 },
 "content": {
 "type": "text",
 "text": "{{canned_response_text}}"
 }
}

If you are building this in Node.js with Express middleware, double-check that you are serializing the object correctly before sending it via axios or fetch. A common mistake is sending the payload as application/x-www-form-urlencoded instead of application/json.

const response = await axios.post(
 `https://api.mypurecloud.com/api/v2/conversations/messaging/${convId}/messages`,
 {
 to: { id: guestId },
 from: { id: agentId },
 content: { type: 'text', text: cannedText }
 },
 {
 headers: {
 'Authorization': `Bearer ${token}`,
 'Content-Type': 'application/json'
 }
 }
);

The 400 Bad Request is almost certainly due to the missing to and from participants. The API needs to know who is sending the message and who is receiving it within the specific conversation context. Check your logs for the exact schema violation error message; it usually points directly to the missing content wrapper.

It depends, but generally… the schema validation is strict on to and from addresses. The point above is correct. The raw text object fails because the API expects a ConversationMessage resource. You must include to, from, and content.

Here is the minimal valid JSON payload for POST /api/v2/conversations/messaging/{conversationId}/messages:

{
 "to": [{
 "id": "channel:web:12345"
 }],
 "from": {
 "id": "agent:67890"
 },
 "content": {
 "type": "text",
 "text": "{{canned_response_text}}"
 }
}

Ensure to is an array of objects with id set to the participant’s channel ID. from must be a single object with the agent or bot ID. The content object requires type (usually text) and text fields.

In my Grafana plugin logic, I validate this structure before sending to prevent 400s. If you are pulling the canned response from /api/v2/organizations/cannedresponses, the content field in the response is already structured correctly. You just need to wrap it in the to/from envelope.

Also, check your OAuth scopes. You need messaging:write and messaging:conversation:write. If the token lacks these, you get 403, not 400, but it’s worth verifying. The Content-Type header must be application/json.

For debugging, enable request logging in your SDK. Look for the validationError key in the response body. It usually points to the missing to array. This pattern is consistent across all messaging endpoints.

You might want to check at the content field structure. The API requires a specific array format for the message body, not a simple string.

{
 "to": "endpoint_id",
 "from": "user_id",
 "content": [{"contentType": "text/plain", "content": "{{canned_response_text}}"}]
}

This aligns with the ConversationMessage schema.

If you check the docs, they mention the content array is mandatory for v2 messaging. I hit this wall last week during a data action transform. Check this guide: https://support.genesys.com/s/article/messaging-payload-structure. The contentType field is also strict; text/plain works, but text/html requires escaping.