Once an agent is deployed, any client can run it. AI Fusion offers two entry points:
/api/v1/agent streams the answer to the client as Server-Sent Events (SSE). This is what the Studio Preview uses, and what you use to embed an agent in your own application.aifusion.Agent flow and the AgentInvoke actor run an agent turn from inside a flow and return the final answer, without streaming.Both run the same runtime: sessions, history, hooks, tools and observability behave identically. This article describes the API, the stream format, the supporting endpoints and the Broadway options.
The AI Fusion endpoints are standard Fabric web services, deployed in the k2_ws LU under the /api/v1/ path. They use Fabric's web-service authentication — a Fabric session (for example, a logged-in browser), an API key sent as Authorization: Bearer <key>, a JWT, OAuth or Basic authentication. See Fabric Web Services Security.
The endpoints run with the caller's permissions — none of them elevates privileges. Grant the calling user or API key's role permission to the web services it needs, and to the Data Products the agent's flows read. Calls made from a multi-tenant space use the tenant prefix in the path, as for any Fabric web service. See Security and Privacy.
GET or POST /api/v1/agent runs one turn of an agent and streams the result.
| Parameter | Required | Description |
agent |
Yes | The agent name — its folder name under <LU>/AI/Agents/. |
lu |
Yes | The Data Product (LU) that holds the agent. |
sessionId |
Yes | The conversation key. Send a new ID to start a conversation and the same ID to continue it. See Sessions and Context. |
content |
Yes | The user message. When the session is paused on ask_user, this is the answer. |
initParams |
No | A JSON object, passed as a string, with the agent's inputs, for example {"customerId":"1001"}. Inputs that the agent's hook flows mark as mandatory must be present, or the request fails before the stream opens with Missing required input(s): .... Get each agent's inputs from the agents endpoint. |
initParams may not contain the key now. A pinned date and time is honored only for Evaluation runs; this endpoint rejects it with HTTP 400.
The response has content type text/event-stream. Do not send an Accept: text/event-stream header — Fabric's content negotiation rejects it with HTTP 406. Omit the Accept header, or use the default.
now in initParams, an unknown agent, or A request is already being processed for this session — is returned as a regular HTTP error.error event with a user-facing message, followed by done. The technical cause is written only to the server log. An agent's on_error hook can replace the message (see Hooks and Agent Inputs).curl -N -G \
-H "Authorization: Bearer $FABRIC_API_KEY" \
--data-urlencode "agent=banking" \
--data-urlencode "lu=customer_bank" \
--data-urlencode "sessionId=6f1c2e7a-5b1d-4c39-9d0e-3a2b8f1e4c55" \
--data-urlencode "content=What is my current checking balance?" \
--data-urlencode 'initParams={"customerId":"1001"}' \
"https://<fabric-host>/api/v1/agent"
-G sends the URL-encoded parameters in the query string of a GET request; -N disables output buffering so events print as they arrive.
Each event is a standard SSE frame — an event: line with the event name and a data: line with a JSON object:
event: text_delta
data: {"text":"Your checking balance is ","agent_id":"6f1c2e7a-5b1d-4c39-9d0e-3a2b8f1e4c55"}
Every frame carries agent_id, the ID of the agent run that produced it. For the main agent this is the sessionId; each sub-agent run has its own ID. Use it to attribute text and tool activity to the right agent when sub-agents run. Most structured events also carry a type field equal to the event name and an ISO-8601 timestamp.
Notes for client developers:
done. Sub-agents emit agent_start and agent_stop too, but never done.done.answer as the answer of record. The streamed text can include narration ("let me check the balance first…") and a delegated sub-agent's commentary. done.answer is exactly the text stored as the turn's answer.agent_stop.usage and every tool_usage event.ask_user and then the regular agent_stop and done. Send the user's answer as the next content on the same sessionId.async function runAgent({ agent, lu, sessionId, content, initParams, onEvent }) {
const query = new URLSearchParams({ agent, lu, sessionId, content });
if (initParams) query.set('initParams', JSON.stringify(initParams));
// No Accept header: Fabric answers 406 to "Accept: text/event-stream".
const response = await fetch(`/api/v1/agent?${query}`, {
method: 'POST',
credentials: 'include', // or headers: { Authorization: 'Bearer <api key>' }
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const emit = (frame) => {
let event = 'message', data = '';
for (const line of frame.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) data = line.slice(5).trim();
}
if (data) onEvent(event, JSON.parse(data));
};
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split('\n\n');
buffer = frames.pop(); // keep an incomplete frame for the next chunk
frames.filter((f) => f.trim()).forEach(emit);
}
if (buffer.trim()) emit(buffer);
}
// Usage
const sessionId = crypto.randomUUID(); // reuse it for the next message of the conversation
let answer = '';
await runAgent({
agent: 'banking', lu: 'customer_bank',
sessionId,
content: 'What is my current checking balance?',
initParams: { customerId: '1001' },
onEvent: (event, data) => {
if (event === 'text_delta' && data.agent_id === sessionId) answer += data.text;
if (event === 'ask_user') console.log('Agent asks:', data.question);
if (event === 'error') console.error(data.message);
if (event === 'done') console.log('Final answer:', data.answer ?? answer, data.aborted ? '(stopped)' : '');
},
});
POST /api/v1/stop-session?sessionId=<id> stops the turn that is running for the session. The open stream ends with done carrying "aborted": true; the partial answer is kept. Returns {"STATUS":"OK"}. See Stopping a Turn.
GET /api/v1/session-status?sessionId=<id> reports whether the session is paused on ask_user. Use it to restore the question UI after a page reload.
The probe never creates a session, so it is safe to call with an unknown ID.
GET /api/v1/conversation-history?sessionId=<id> returns the session's steps — the user messages and final answers — for rendering a transcript.
GET /api/v1/chats?agentName=<agent>&limit=<n> lists the calling user's recent conversations with one agent, newest first. The user is taken from the authenticated session and cannot be passed as a parameter. limit defaults to 20 and is capped at 100. Each entry has sessionId, name (the generated chat name), agentName, createdAt, lastModifiedAt and status.
Two GraphIt endpoints return the execution trace that the Studio trace panel shows — executions, messages, tool calls and token usage:
GET /api/v1/trace-history?chatid=<sessionId> — the trace of a session; the optional stepId and messageId parameters narrow it to one step or message.GET /api/v1/trace-conversation?chatid=<sessionId> — the trace of all steps of the conversation.Call them only for sessions that have run: looking up an unknown ID creates an empty session instance.
/api/v1/feedback records the end user's thumbs-up or thumbs-down and notes per answer (POST to add, PUT to change, DELETE to remove, GET to read). The submitting user is recorded automatically. See Feedback.
/api/v1/sessionids (parameters luName, iid) is kept from AI Fusion 2.x. To find agent conversations, use chats for the current user, or the Observation conversation list.
GET /api/v1/agents lists the deployed agents across all Data Products; add luName to filter by one LU. Each entry contains:
| Field | Description |
name, lu |
The agent and its Data Product — the agent and lu parameters for /api/v1/agent. |
description |
The agent's description. |
llm_interface, thinking |
The configured LLM interface and thinking level. |
flows, mcp_interfaces, subagents, skills |
The agent's flow tools, MCP interfaces, sub-agents and skills. |
planning, memory |
Whether planning and the memory setting are on. |
input_params |
The agent's inputs, inferred from its hook flows: a list of {name, type, required}. Send them in initParams. See Hooks and Agent Inputs. |
GET /api/v1/skills (optional luName) lists the LU-wide skills — those under <LU>/AI/Skills/ and shared skills — with name, lu, description and shared. Agent-private skills are not listed.
The REST API runs the aifusion.Agent flow, which wraps the Agent actor. You can run the same flow yourself, for example from a Fabric command:
broadway aifusion.Agent 'agent'='banking' 'luName'='customer_bank' 'sessionId'='6f1c2e7a-5b1d-4c39-9d0e-3a2b8f1e4c55' 'content'='What is my balance?' 'initParams'='{"customerId":"1001"}'
The flow returns the turn's final answer (the actor's response output, exposed by the flow as result). When the turn pauses on ask_user, the returned value is the question. Outside an HTTP request there is no event stream, and a failure during the run is not raised: the answer comes back empty and the error is recorded in the session. Check the session's status — for example with the session-status endpoint — when you need to tell the outcomes apart.
aifusion.Agent lives in the aifusion Data Product, so an InnerFlow actor in another Data Product cannot reach it. The AgentInvoke actor (AI Fusion extension 3.0.5 and later) runs an agent turn from a flow in any Data Product and returns its final answer.
agent, luName (a Data Product picker; empty means aifusion) and, optionally, interface on the actor.content, sessionId and initParams (a map, for example from MapCreate, or a JSON string). Use a RandomUUID actor for a new session, or reuse a sessionId to continue a conversation.response is the final answer.The invoked agent runs a full turn — session state, history, hooks, tools and observability are recorded under the sessionId exactly as for an API call. If the calling flow runs inside a streaming request, the agent's events go to that request's stream.
The Broadway LLM actors are still shipped for custom flows that need a direct LLM call:
These actors are not the agent runtime: they do not read agent.yaml and have no sessions, hooks, skills, sub-agents or Studio support. Use them inside tool or hook flows — for example a flow tool that classifies text — not as a substitute for an agent. The prompt-construction actors that go with them (LLMConst, LLMAppend) are described in the legacy article LLM Calls Utility Actors; its parameter tables predate AI Fusion 3, so check each actor's documentation in Studio for the current inputs and outputs.
Next article: LLM Connectors
Once an agent is deployed, any client can run it. AI Fusion offers two entry points:
/api/v1/agent streams the answer to the client as Server-Sent Events (SSE). This is what the Studio Preview uses, and what you use to embed an agent in your own application.aifusion.Agent flow and the AgentInvoke actor run an agent turn from inside a flow and return the final answer, without streaming.Both run the same runtime: sessions, history, hooks, tools and observability behave identically. This article describes the API, the stream format, the supporting endpoints and the Broadway options.
The AI Fusion endpoints are standard Fabric web services, deployed in the k2_ws LU under the /api/v1/ path. They use Fabric's web-service authentication — a Fabric session (for example, a logged-in browser), an API key sent as Authorization: Bearer <key>, a JWT, OAuth or Basic authentication. See Fabric Web Services Security.
The endpoints run with the caller's permissions — none of them elevates privileges. Grant the calling user or API key's role permission to the web services it needs, and to the Data Products the agent's flows read. Calls made from a multi-tenant space use the tenant prefix in the path, as for any Fabric web service. See Security and Privacy.
GET or POST /api/v1/agent runs one turn of an agent and streams the result.
| Parameter | Required | Description |
agent |
Yes | The agent name — its folder name under <LU>/AI/Agents/. |
lu |
Yes | The Data Product (LU) that holds the agent. |
sessionId |
Yes | The conversation key. Send a new ID to start a conversation and the same ID to continue it. See Sessions and Context. |
content |
Yes | The user message. When the session is paused on ask_user, this is the answer. |
initParams |
No | A JSON object, passed as a string, with the agent's inputs, for example {"customerId":"1001"}. Inputs that the agent's hook flows mark as mandatory must be present, or the request fails before the stream opens with Missing required input(s): .... Get each agent's inputs from the agents endpoint. |
initParams may not contain the key now. A pinned date and time is honored only for Evaluation runs; this endpoint rejects it with HTTP 400.
The response has content type text/event-stream. Do not send an Accept: text/event-stream header — Fabric's content negotiation rejects it with HTTP 406. Omit the Accept header, or use the default.
now in initParams, an unknown agent, or A request is already being processed for this session — is returned as a regular HTTP error.error event with a user-facing message, followed by done. The technical cause is written only to the server log. An agent's on_error hook can replace the message (see Hooks and Agent Inputs).curl -N -G \
-H "Authorization: Bearer $FABRIC_API_KEY" \
--data-urlencode "agent=banking" \
--data-urlencode "lu=customer_bank" \
--data-urlencode "sessionId=6f1c2e7a-5b1d-4c39-9d0e-3a2b8f1e4c55" \
--data-urlencode "content=What is my current checking balance?" \
--data-urlencode 'initParams={"customerId":"1001"}' \
"https://<fabric-host>/api/v1/agent"
-G sends the URL-encoded parameters in the query string of a GET request; -N disables output buffering so events print as they arrive.
Each event is a standard SSE frame — an event: line with the event name and a data: line with a JSON object:
event: text_delta
data: {"text":"Your checking balance is ","agent_id":"6f1c2e7a-5b1d-4c39-9d0e-3a2b8f1e4c55"}
Every frame carries agent_id, the ID of the agent run that produced it. For the main agent this is the sessionId; each sub-agent run has its own ID. Use it to attribute text and tool activity to the right agent when sub-agents run. Most structured events also carry a type field equal to the event name and an ISO-8601 timestamp.
Notes for client developers:
done. Sub-agents emit agent_start and agent_stop too, but never done.done.answer as the answer of record. The streamed text can include narration ("let me check the balance first…") and a delegated sub-agent's commentary. done.answer is exactly the text stored as the turn's answer.agent_stop.usage and every tool_usage event.ask_user and then the regular agent_stop and done. Send the user's answer as the next content on the same sessionId.async function runAgent({ agent, lu, sessionId, content, initParams, onEvent }) {
const query = new URLSearchParams({ agent, lu, sessionId, content });
if (initParams) query.set('initParams', JSON.stringify(initParams));
// No Accept header: Fabric answers 406 to "Accept: text/event-stream".
const response = await fetch(`/api/v1/agent?${query}`, {
method: 'POST',
credentials: 'include', // or headers: { Authorization: 'Bearer <api key>' }
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const emit = (frame) => {
let event = 'message', data = '';
for (const line of frame.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) data = line.slice(5).trim();
}
if (data) onEvent(event, JSON.parse(data));
};
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split('\n\n');
buffer = frames.pop(); // keep an incomplete frame for the next chunk
frames.filter((f) => f.trim()).forEach(emit);
}
if (buffer.trim()) emit(buffer);
}
// Usage
const sessionId = crypto.randomUUID(); // reuse it for the next message of the conversation
let answer = '';
await runAgent({
agent: 'banking', lu: 'customer_bank',
sessionId,
content: 'What is my current checking balance?',
initParams: { customerId: '1001' },
onEvent: (event, data) => {
if (event === 'text_delta' && data.agent_id === sessionId) answer += data.text;
if (event === 'ask_user') console.log('Agent asks:', data.question);
if (event === 'error') console.error(data.message);
if (event === 'done') console.log('Final answer:', data.answer ?? answer, data.aborted ? '(stopped)' : '');
},
});
POST /api/v1/stop-session?sessionId=<id> stops the turn that is running for the session. The open stream ends with done carrying "aborted": true; the partial answer is kept. Returns {"STATUS":"OK"}. See Stopping a Turn.
GET /api/v1/session-status?sessionId=<id> reports whether the session is paused on ask_user. Use it to restore the question UI after a page reload.
The probe never creates a session, so it is safe to call with an unknown ID.
GET /api/v1/conversation-history?sessionId=<id> returns the session's steps — the user messages and final answers — for rendering a transcript.
GET /api/v1/chats?agentName=<agent>&limit=<n> lists the calling user's recent conversations with one agent, newest first. The user is taken from the authenticated session and cannot be passed as a parameter. limit defaults to 20 and is capped at 100. Each entry has sessionId, name (the generated chat name), agentName, createdAt, lastModifiedAt and status.
Two GraphIt endpoints return the execution trace that the Studio trace panel shows — executions, messages, tool calls and token usage:
GET /api/v1/trace-history?chatid=<sessionId> — the trace of a session; the optional stepId and messageId parameters narrow it to one step or message.GET /api/v1/trace-conversation?chatid=<sessionId> — the trace of all steps of the conversation.Call them only for sessions that have run: looking up an unknown ID creates an empty session instance.
/api/v1/feedback records the end user's thumbs-up or thumbs-down and notes per answer (POST to add, PUT to change, DELETE to remove, GET to read). The submitting user is recorded automatically. See Feedback.
/api/v1/sessionids (parameters luName, iid) is kept from AI Fusion 2.x. To find agent conversations, use chats for the current user, or the Observation conversation list.
GET /api/v1/agents lists the deployed agents across all Data Products; add luName to filter by one LU. Each entry contains:
| Field | Description |
name, lu |
The agent and its Data Product — the agent and lu parameters for /api/v1/agent. |
description |
The agent's description. |
llm_interface, thinking |
The configured LLM interface and thinking level. |
flows, mcp_interfaces, subagents, skills |
The agent's flow tools, MCP interfaces, sub-agents and skills. |
planning, memory |
Whether planning and the memory setting are on. |
input_params |
The agent's inputs, inferred from its hook flows: a list of {name, type, required}. Send them in initParams. See Hooks and Agent Inputs. |
GET /api/v1/skills (optional luName) lists the LU-wide skills — those under <LU>/AI/Skills/ and shared skills — with name, lu, description and shared. Agent-private skills are not listed.
The REST API runs the aifusion.Agent flow, which wraps the Agent actor. You can run the same flow yourself, for example from a Fabric command:
broadway aifusion.Agent 'agent'='banking' 'luName'='customer_bank' 'sessionId'='6f1c2e7a-5b1d-4c39-9d0e-3a2b8f1e4c55' 'content'='What is my balance?' 'initParams'='{"customerId":"1001"}'
The flow returns the turn's final answer (the actor's response output, exposed by the flow as result). When the turn pauses on ask_user, the returned value is the question. Outside an HTTP request there is no event stream, and a failure during the run is not raised: the answer comes back empty and the error is recorded in the session. Check the session's status — for example with the session-status endpoint — when you need to tell the outcomes apart.
aifusion.Agent lives in the aifusion Data Product, so an InnerFlow actor in another Data Product cannot reach it. The AgentInvoke actor (AI Fusion extension 3.0.5 and later) runs an agent turn from a flow in any Data Product and returns its final answer.
agent, luName (a Data Product picker; empty means aifusion) and, optionally, interface on the actor.content, sessionId and initParams (a map, for example from MapCreate, or a JSON string). Use a RandomUUID actor for a new session, or reuse a sessionId to continue a conversation.response is the final answer.The invoked agent runs a full turn — session state, history, hooks, tools and observability are recorded under the sessionId exactly as for an API call. If the calling flow runs inside a streaming request, the agent's events go to that request's stream.
The Broadway LLM actors are still shipped for custom flows that need a direct LLM call:
These actors are not the agent runtime: they do not read agent.yaml and have no sessions, hooks, skills, sub-agents or Studio support. Use them inside tool or hook flows — for example a flow tool that classifies text — not as a substitute for an agent. The prompt-construction actors that go with them (LLMConst, LLMAppend) are described in the legacy article LLM Calls Utility Actors; its parameter tables predate AI Fusion 3, so check each actor's documentation in Studio for the current inputs and outputs.
Next article: LLM Connectors