The Sync API In Parts feature transforms the traditional request-response pattern by breaking large responses into manageable, progressive chunks delivered across multiple synchronous HTTP requests. Instead of waiting for a complete response, clients receive partial results immediately and fetch successive segments until the conversation completes. This approach combines the responsiveness and interactivity of streaming protocols with the simplicity and reliability of standard HTTP.
Key Benefits
-
Enhanced User Experience: Enables chat and voice channels to feel more natural by delivering responses progressively. Clients receive partial results as they are generated, so users don’t have to wait for the entire response. This allows for faster perceived performance and more interactive experiences.
-
Event-Driven Control: Supports multiple response types (
message
,typing
,NextPartRequest
,ResponseCompleted
,endOfConversation
), giving developers fine-grained control over the interaction flow. For example, typing indicators can be shown in chat interfaces, or realistic typing sounds and background noise can be triggered in voice applications to enhance conversational authenticity. -
Simple Integration: Provides streaming-like responsiveness through standard HTTP requests without requiring complex WebSocket protocols or asynchronous infrastructure. Works seamlessly with existing REST API clients.
Quick Start Guide
Enable Parts Mode: Add SendResponseInParts: true
header to all requests
Start: Send initial message with type: "message"
Continue: Send type: "NextPartRequest"
requests until completion
Stop: When you receive type: "ResponseCompleted"
or type: "endOfConversation"
Protocol Overview
Step 1: Initial Request to Start Conversation
The client initiates the interaction by sending an Activity object with type="message"
. The text
field should contain the user's input (can be empty in some scenarios, depending on context).
Here's an example of a request:
curl --location '{{BaseUrl}}/magpie/ext-api/messages/synchronized' \
--header 'Content-Type: application/json' \
--header 'Project: {{ProjectName}}' \
--header 'X-Knovvu-Conversation-Id: {{ConversationId}}' \
--header 'SendResponseInParts: true' \
--header 'Authorization: Bearer {{AccessToken}}' \
--data '{
"text": "{{UserInput}}",
"conversation": {
"id": "{{ConversationId}}"
},
"channelId": "ivr-external",
"type": "message",
"attachments": [],
"channelData": {
"ResponseType": "Text"
}
}'
Expected Response:
{
"type": "message",
"id": "d9efa756-4f8b-4db8-b9f1-dc85e6fc712c",
"timestamp": null,
"localTimestamp": null,
"localTimezone": null,
"serviceUrl": "http://localhost",
"channelId": "ivr-external",
"from": null,
"conversation": {
"isGroup": null,
"conversationType": null,
"id": "{{ConversationId}}",
"name": null,
"aadObjectId": null,
"role": null,
"tenantId": null
},
"recipient": null,
"textFormat": null,
"attachmentLayout": null,
"membersAdded": null,
"membersRemoved": null,
"reactionsAdded": null,
"reactionsRemoved": null,
"topicName": null,
"historyDisclosed": null,
"locale": null,
"text": "{{BotResponse}}",
"speak": null,
"inputHint": null,
"summary": null,
"suggestedActions": null,
"attachments": [],
"entities": null,
"channelData": {
"IsSynchronizer": false,
"HasExternalAudioContent": false,
"SrConfidence": null,
"BargeInType": 0,
"CustomProperties": {},
"CustomAction": null,
"CustomActionData": null,
"ProjectName": "{{ProjectName}}",
"ResponseType": 0,
"EndUser": {
"Name": null,
"Phone": null,
"Email": null,
"Twitter": null,
"Id": null
}
},
"action": null,
"replyToId": null,
"label": null,
"valueType": null,
"value": null,
"name": null,
"relatesTo": null,
"code": null,
"expiration": null,
"importance": null,
"deliveryMode": null,
"listenFor": null,
"textHighlights": null,
"semanticAction": null,
"callerId": null
}
Step 2: Fetching Next Parts of the Response
After sending the initial message, the client must repeatedly send requests to retrieve incremental results with type="NextPartRequest"
and text=""
(empty string).
Supported Response Types:
When the client sends NextPartRequest
, the server replies with one of the following response types:
Response Type | Description | Client Action |
---|---|---|
typing |
Server is still processing | Continue with NextPartRequest |
message |
Partial response content | Continue with NextPartRequest |
ResponseCompleted |
Full response delivered | Stop or send new message |
endOfConversation |
Conversation closed | Start new conversation |
Error |
Protocol misuse or internal issue | Handle error appropriately |
The server responds with partial outputs until either type="ResponseCompleted"
or type="endOfConversation"
response is emitted.
Here's an example of a request:
curl --location '{{BaseUrl}}/magpie/ext-api/messages/synchronized' \
--header 'Content-Type: application/json' \
--header 'Project: {{ProjectName}}' \
--header 'X-Knovvu-Conversation-Id: {{ConversationId}}' \
--header 'SendResponseInParts: true' \
--header 'Authorization: Bearer {{AccessToken}}' \
--data '{
"text": "",
"conversation": {
"id": "{{ConversationId}}"
},
"channelId": "ivr-external",
"type": "NextPartRequest",
"attachments": [],
"channelData": {
"ResponseType": "Text"
}
}'
Expected Response if More Parts Available
{
"type": "message",
"id": "be303d36-5242-403e-b00a-458d79062181",
"timestamp": null,
"localTimestamp": null,
"localTimezone": null,
"serviceUrl": "http://localhost",
"channelId": "ivr-external",
"from": null,
"conversation": {
"isGroup": null,
"conversationType": null,
"id": "{{ConversationId}}",
"name": null,
"aadObjectId": null,
"role": null,
"tenantId": null
},
"recipient": null,
"textFormat": null,
"attachmentLayout": null,
"membersAdded": null,
"membersRemoved": null,
"reactionsAdded": null,
"reactionsRemoved": null,
"topicName": null,
"historyDisclosed": null,
"locale": null,
"text": "{{PartialBotResponse}}",
"speak": null,
"inputHint": null,
"summary": null,
"suggestedActions": null,
"attachments": [],
"entities": null,
"channelData": {
"IsSynchronizer": false,
"HasExternalAudioContent": false,
"SrConfidence": null,
"BargeInType": 0,
"CustomProperties": {},
"CustomAction": null,
"CustomActionData": null,
"ProjectName": "{{ProjectName}}",
"ResponseType": 0,
"EndUser": {
"Name": null,
"Phone": null,
"Email": null,
"Twitter": null,
"Id": null
}
},
"action": null,
"replyToId": null,
"label": null,
"valueType": null,
"value": null,
"name": null,
"relatesTo": null,
"code": null,
"expiration": null,
"importance": null,
"deliveryMode": null,
"listenFor": null,
"textHighlights": null,
"semanticAction": null,
"callerId": null
}
Expected Response If Response Completed
{
"type": "ResponseCompleted",
"id": null,
"timestamp": null,
"localTimestamp": null,
"localTimezone": null,
"serviceUrl": "http://localhost",
"channelId": "ivr-external",
"from": null,
"conversation": {
"isGroup": null,
"conversationType": null,
"id": "{{ConversationId}}",
"name": null,
"aadObjectId": null,
"role": null,
"tenantId": null
},
"recipient": null,
"textFormat": null,
"attachmentLayout": null,
"membersAdded": null,
"membersRemoved": null,
"reactionsAdded": null,
"reactionsRemoved": null,
"topicName": null,
"historyDisclosed": null,
"locale": null,
"text": null,
"speak": null,
"inputHint": null,
"summary": null,
"suggestedActions": null,
"attachments": null,
"entities": null,
"channelData": {
"IsSynchronizer": false,
"HasExternalAudioContent": false,
"SrConfidence": null,
"BargeInType": 0,
"CustomProperties": {},
"CustomAction": null,
"CustomActionData": null,
"ProjectName": "{{ProjectName}}",
"ResponseType": 0,
"EndUser": {
"Name": null,
"Phone": null,
"Email": null,
"Twitter": null,
"Id": null
}
},
"action": null,
"replyToId": null,
"label": null,
"valueType": null,
"value": null,
"name": null,
"relatesTo": null,
"code": null,
"expiration": null,
"importance": null,
"deliveryMode": null,
"listenFor": null,
"textHighlights": null,
"semanticAction": null,
"callerId": null
}
Expected Response If Conversation Ends
{
"type": "endOfConversation",
"id": null,
"timestamp": null,
"localTimestamp": null,
"localTimezone": null,
"serviceUrl": "http://localhost",
"channelId": "ivr-external",
"from": null,
"conversation": {
"isGroup": null,
"conversationType": null,
"id": "{{ConversationId}}",
"name": null,
"aadObjectId": null,
"role": null,
"tenantId": null
},
"recipient": null,
"textFormat": null,
"attachmentLayout": null,
"membersAdded": null,
"membersRemoved": null,
"reactionsAdded": null,
"reactionsRemoved": null,
"topicName": null,
"historyDisclosed": null,
"locale": null,
"text": null,
"speak": null,
"inputHint": null,
"summary": null,
"suggestedActions": null,
"attachments": null,
"entities": null,
"channelData": {
"IsSynchronizer": false,
"HasExternalAudioContent": false,
"SrConfidence": null,
"BargeInType": 0,
"CustomProperties": {},
"CustomAction": null,
"CustomActionData": null,
"ProjectName": "{{ProjectName}}",
"ResponseType": 0,
"EndUser": {
"Name": null,
"Phone": null,
"Email": null,
"Twitter": null,
"Id": null
}
},
"action": null,
"replyToId": null,
"label": null,
"valueType": null,
"value": null,
"name": null,
"relatesTo": null,
"code": null,
"expiration": null,
"importance": null,
"deliveryMode": null,
"listenFor": null,
"textHighlights": null,
"semanticAction": null,
"callerId": null
}
Barge-in support (Interrupting a Response)
Clients may interrupt an ongoing response by sending a new message request with a BargeInType
header. When barge-in type is sent, the server stops current generation and begins generating a response for the new input.
Supported BargeInType Values:
Voice
- Voice interruption detectedDigit
- DTMF/keypad input detectedNone
- No interruption
Here's an example of a request:
curl --location '{{BaseUrl}}/magpie/ext-api/messages/synchronized' \
--header 'Content-Type: application/json' \
--header 'Project: {{ProjectName}}' \
--header 'X-Knovvu-Conversation-Id: {{ConversationId}}' \
--header 'SendResponseInParts: true' \
--header 'BargeInType: Voice' \
--header 'Authorization: Bearer {{AccessToken}}' \
--data '{
"text": "",
"conversation": {
"id": "{{ConversationId}}"
},
"channelId": "ivr-external",
"type": "NextPartRequest",
"attachments": [],
"channelData": {
"ResponseType": "Text"
}
}'
Common Protocol Errors
1. Conversation must start with a message
If the first Activity is not a message, the server rejects the request. Ensure the first request uses type: "message"
.
Error Response:
{
"error": {
"code": null,
"message": "Protocol Error. A conversation can be started only with a message type of Activity",
"details": null
}
}
2. Don't forget to send NextPartRequest requests
After the initial message, client must continue with NextPartRequest
requests.
Error Response:
{
"error": {
"code": null,
"message": "Protocol error, send NextPartRequest until ResponseCompleted or endOfConversation. Or set BargeInType explicitly",
"details": null
}
}
3. Do not send NextPartRequest
after completion
Once ResponseCompleted
or endOfConversation
has been received, the client must stop sending NextPartRequest
. If violated, the server will return an error.
Error Response:
{
"error": {
"code": null,
"message": "Protocol error, there is no part to send. Send an input first",
"details": null
}
}
Summary
✅ Start with type: message
and user input
✅ Continue with type: NextPartRequest
and empty text
✅ Stop after ResponseCompleted
or endOfConversation
✅ Handle typing events for enhanced UX
✅ Use BargeInType
headers for interruptions
✅ Implement proper error handling and retries
This protocol provides a simple yet powerful way to create responsive, interactive conversations using standard HTTP requests, delivering the benefits of streaming while maintaining the simplicity and reliability of synchronous communication.