Pular para o conteúdo principal

Messages & Streaming

This page covers the core chat loop: sending a user message, getting the agent's reply (optionally streamed), executing any actions the reply asks for, and reporting the results back so the agent can continue.

Actions in a reply are wire envelopes of { id, type, payload } — see Agent Actions for the full set of action types and payload shapes.

Send a message

POST /agent/message

Auth: required.

Body

FieldTypeRequiredDescription
contentstringYesThe user's message text.
sessionIdstringNoExisting session to continue. If omitted, a new session is created.
contextobjectNoOpaque context object describing what's currently visible in the app — required for the agent to reason about the UI. context.currentScreen?: string is a common and recommended field.
mcpVersionstringNoVersion of your MCP document, for correlating the reply with a specific app build.

Response — 200

{
"success": true,
"data": {
"sessionId": "...",
"message": {
"id": "...",
"role": "assistant",
"content": "...",
"summary": "...",
"actions": [
{ "id": "...", "type": "navigate", "payload": { ... } }
]
},
"usage": {
"tokensUsed": 0,
"modelUsed": "...",
"latencyMs": 0
}
}
}

A request that fails body validation (e.g. missing content) returns 422 with error.code: "VALIDATION_ERROR".

curl

curl https://api.appilots.com/api/v1/agent/message \
-X POST \
-H "Authorization: Bearer ak_xxxxxxxxxxxx..." \
-H "Content-Type: application/json" \
-d '{
"content": "Take me to my profile",
"sessionId": "SESSION_ID",
"context": { "currentScreen": "Home" }
}'

TypeScript

const res = await fetch("https://api.appilots.com/api/v1/agent/message", {
method: "POST",
headers: {
Authorization: "Bearer ak_xxxxxxxxxxxx...",
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "Take me to my profile",
sessionId,
context: { currentScreen: "Home" },
}),
});
const { data } = await res.json();

Stream a message

POST /agent/message/stream

Same request body as POST /agent/message, but the response is Content-Type: text/event-stream (Server-Sent Events) instead of a single JSON body — use this to show the assistant's reply incrementally as it's generated.

Auth: required.

Events

Each SSE frame is a data: {...} line containing JSON with a type field:

typeFiresPayload
sessionOnce, early{ "type": "session", "sessionId": "..." }
text-deltaRepeated{ "type": "text-delta", "content": "..." } — incremental text; concatenate across events to build the full reply.
tool-callZero or more{ "type": "tool-call", "action": { "id", "type", "payload" } }
doneOnce, terminal{ "type": "done", "messageId": "...", "usage?": { "tokensUsed": 0 } } — the stream ends after this event.

curl

curl https://api.appilots.com/api/v1/agent/message/stream \
-X POST \
-H "Authorization: Bearer ak_xxxxxxxxxxxx..." \
-H "Content-Type: application/json" \
-N \
-d '{
"content": "Take me to my profile",
"sessionId": "SESSION_ID",
"context": { "currentScreen": "Home" }
}'

TypeScript

const res = await fetch("https://api.appilots.com/api/v1/agent/message/stream", {
method: "POST",
headers: {
Authorization: "Bearer ak_xxxxxxxxxxxx...",
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "Take me to my profile",
sessionId,
context: { currentScreen: "Home" },
}),
});

const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });

for (const line of buffer.split("\n\n")) {
if (!line.startsWith("data: ")) continue;
const event = JSON.parse(line.slice(6));
// handle event.type: "session" | "text-delta" | "tool-call" | "done"
}
}

Error behavior: if a connection error occurs before any SSE event is emitted, the endpoint falls back to a plain JSON error body (using the standard error envelope) instead of an SSE stream. Treat "connection error before any event" as safe to retry via the non-streaming POST /agent/message endpoint.

React Native

React Native's fetch doesn't support incremental reads, so the Appilots SDK uses XMLHttpRequest internally for this endpoint on RN. If you're integrating from a browser, Node, or curl, a normal streaming fetch/EventSource-style read works fine.

Continue a turn

POST /agent/continue

After your client executes the actions from a previous /agent/message (or /agent/message/stream) response, call this endpoint with the results to get the agent's next step. A single user turn can span multiple hops: keep calling /agent/continue and executing any new actions it returns as long as the response says the turn isn't done yet.

Auth: required.

Body

FieldTypeRequiredDescription
sessionIdstringYesThe session this turn belongs to.
resultsarrayYesOutcomes of the actions you just executed: { actionId, type, success, error?, summary? }.
contextobjectYesSame opaque UI context object as /agent/message.
hopnumberNoStart at 1 for the first continuation after the initial message, increment by 1 each subsequent call within the same turn.

Response — 200

{
"success": true,
"data": {
"sessionId": "...",
"message": {
"id": "...",
"role": "assistant",
"content": "...",
"actions": [ { "id": "...", "type": "...", "payload": { ... } } ]
},
"usage": { "tokensUsed": 0, "modelUsed": "...", "latencyMs": 0 },
"isDone": false,
"hop": 2
}
}

Keep calling /agent/continue while isDone is false and actions is non-empty. A turn has a bounded number of steps; once the server decides no further hops are useful, it returns isDone: true even without a final natural stopping point — treat that as the signal to stop the loop regardless of whether actions is empty.

A request that fails body validation returns 422 (VALIDATION_ERROR). A sessionId that doesn't exist or belongs to a different project returns 404 (NOT_FOUND).

curl

curl https://api.appilots.com/api/v1/agent/continue \
-X POST \
-H "Authorization: Bearer ak_xxxxxxxxxxxx..." \
-H "Content-Type: application/json" \
-d '{
"sessionId": "SESSION_ID",
"hop": 1,
"results": [
{ "actionId": "ACTION_ID", "type": "navigate", "success": true }
],
"context": { "currentScreen": "Profile" }
}'

TypeScript

let hop = 1;
let isDone = false;

while (!isDone) {
const res = await fetch("https://api.appilots.com/api/v1/agent/continue", {
method: "POST",
headers: {
Authorization: "Bearer ak_xxxxxxxxxxxx...",
"Content-Type": "application/json",
},
body: JSON.stringify({
sessionId,
hop,
results: [{ actionId: "ACTION_ID", type: "navigate", success: true }],
context: { currentScreen: "Profile" },
}),
});
const { data } = await res.json();
isDone = data.isDone;
hop += 1;
// execute data.message.actions, then loop with their results
}
  • Agent Actions — action types and payload shapes
  • Actions — reporting individual action outcomes outside the /agent/continue loop
  • Errors — full error code reference