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,ResponseCompleted,endOfConversation,SpeechRecognized,NotEnoughCreditOrInvalidLicense,Error), 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", type: "endOfConversation", type: "NotEnoughCreditOrInvalidLicense", or type: "Error"
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).
Note: Before the session is started, the system performs a License Pre-Check against the License Server. If no credit is available or the license is invalid, the response will be
type = "NotEnoughCreditOrInvalidLicense"and the flow will not be initiated. See the License Pre-Check Response section below.
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 |
SpeechRecognized |
SR recognition result | Continue with NextPartRequest |
NotEnoughCreditOrInvalidLicense |
License pre-check failed — no credit or invalid license | Stop. Display the localized message in text to the user. |
Error |
Protocol misuse or internal issue | Handle error appropriately |
The server responds with partial outputs until a terminal response is received: type="ResponseCompleted", type="endOfConversation", type="NotEnoughCreditOrInvalidLicense", or an Error response.
In addition to these return types, a new return type can be added. The
NextPartRequestmust continue to be sent untiltype="ResponseCompleted",type="endOfConversation",type="NotEnoughCreditOrInvalidLicense", or anErrorresponse is received on the client side.Additionally, IVR clients may consider it an error condition if the incoming
messagetype does not include audio.
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"
}
}'
2.1. If More Parts Available
Expected response:
{
"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
}
2.2. If Response Completed
Expected response:
{
"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
}
2.3. If Conversation Ends
Expected response:
{
"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
}
License Pre-Check Response (NotEnoughCreditOrInvalidLicense)
Before a session is started, the system performs a license availability pre-check against the License Server. If no credit is available or the license is invalid, the server returns a response with type = "NotEnoughCreditOrInvalidLicense" to the initial message request, and the parts flow is never started.
When This Response Is Returned
RemainingCreditCount <= 0on LDM- The license is invalid (expired, missing required bits, etc.)
- The License Server cannot be reached and credit cannot be verified
Client Action
- Display the localized message in the
textfield to the end user - Do not send
NextPartRequest— there is no parts flow to continue - Treat this response as a terminal state, equivalent to
endOfConversation - Optionally retry later with a new conversation ID
The text field is returned in the language configured for the project/tenant. The full list of localized messages and the underlying pre-check logic are documented in the License Pre-Check page.
If License Pre-Check Fails:
Expected Response
{
"type": "NotEnoughCreditOrInvalidLicense",
"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": "We are currently unable to start the conversation. Please try again later.",
"speak": null,
"inputHint": null,
"summary": null,
"suggestedActions": null,
"attachments": null,
"entities": null,
"channelData": {
"IsSynchronizer": false,
"HasExternalAudioContent": false,
"SrConfidence": null,
"BargeInType": 0,
"CustomProperties": {},
"SrWordDetails": [],
"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 HTTP Response Code:
403 Forbidden
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 HTTP Response Code:
400 Bad Request
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, endOfConversation, NotEnoughCreditOrInvalidLicense, or an Error response has been received, the client must stop sending NextPartRequest. If violated, the server will return an error.
Error HTTP Response Code:
400 Bad Request
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, endOfConversation, NotEnoughCreditOrInvalidLicense, or an Error response
✅ 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.
