Skip to content

API Reference

Local examples use productId as the device agent ID, deviceId as the real device ID, and HTTP products paths as device agents.

txt
HTTP: http://127.0.0.1:3000
Voice WebSocket: ws://127.0.0.1:3001/ws/voice
Voice HTTP: http://127.0.0.1:3001/api/chat, /api/vision/frames

When direct HTTPS is enabled, use https:// for the main HTTP endpoint. Main HTTP TLS does not enable TLS for the MQTT broker or voice service; configure those endpoints separately and use wss:// when browsers connect to them. For a private CA, pass its CA file to clients instead of disabling certificate verification.

The Gateway does not provide user authentication, tenant isolation, session ownership, or resource-level authorization. HTTP, SSE, and voice WebSocket endpoints are for trusted systems or controlled clients only. Put them behind an authenticating and authorizing gateway before multi-user or public access. HTTPS/WSS protects traffic; it is not access control.

Choose a Protocol

ScenarioRecommended ProtocolNotes
Real devices stay online, report state, and receive commandsMQTTFits device-side connections, state synchronization, and command responses.
Other agents discover and call an A2A-enabled Device AgentA2A over MQTTUses MQTT v5 discovery and request/reply topics.
Business systems create or run multi-agent Scenes and one-off TasksHTTP + SSESubmit requests over HTTP and receive planning, step, and final-result events over SSE.
Business systems, console extensions, or automation scripts call Device AgentHTTPFits one-shot requests, queries, and command dispatch.
Realtime voice interactionWebSocketFits continuous audio input, realtime ASR results, and TTS output.
Browser or device clients connect to an MQTT broker over WebSocketMQTT over WebSocketThis is an MQTT transport mode, not the Device Agent voice WebSocket.

MQTT

MQTT is used for device-side access. Devices use MQTT to come online, report state, receive commands, return command results, and publish events. Broker URL, credentials, and topic templates follow the console configuration.

DirectionTopicPurpose
MQTT client -> Device Agentdevice-agent/{productId}/inSend a text request to a device agent.
Device Agent -> MQTT clientdevice-agent/{productId}/outReturn a device agent reply.
MQTT client -> Device Agentdevice-agent/{productId}/device/{deviceId}/inSend a text request with device context.
Device Agent -> MQTT clientdevice-agent/{productId}/device/{deviceId}/outReturn a reply with device context.
Device Agent -> devicedevice-agent/{productId}/device/{deviceId}/commandsSend device commands.
Device -> Device Agentdevice-agent/{productId}/device/{deviceId}/responsesReturn command results.
Device -> Device Agentv1/{productId}/{deviceId}/telemetryReport online status and current state.
Device -> Device Agentv1/{productId}/{deviceId}/eventReport device events.
Device -> Device Agentdevice-agent/{productId}/device/{deviceId}/ntp/requestRequest time synchronization.
Device Agent -> devicedevice-agent/{productId}/device/{deviceId}/ntp/responseReturn time synchronization data.

Text request payload:

json
{
  "id": "req-001",
  "prompt": "Check the current temperature",
  "sessionId": "session-default:thermostat:thermostat-001",
  "metadata": {
    "source": "mqtt-client"
  }
}

Device agent reply payload:

json
{
  "sessionId": "session-default:thermostat:thermostat-001",
  "text": "The current temperature is 28 degrees.",
  "metadata": {
    "timestamp": "2026-05-11T10:00:00.000Z"
  },
  "timestamp": "2026-05-11T10:00:00.000Z"
}

One chat turn produces multiple messages. Progress messages have an empty top-level text and carry progress in metadata.status:

metadata.status.typeKey fields
start, thinkingCurrent execution stage.
text_blockIncremental text in metadata.status.text.
tool_start, tool_endTool name, input, output, or error.
complete, errorCurrent execution result. The final complete reply still arrives in the top-level text of a regular message.

To interrupt or clear a session, publish an empty prompt to the original input topic with metadata.interrupt: true or metadata.clearSession: true. The acknowledgement uses metadata.status.type: complete; metadata.replyTo matches the request id.

For more payload rules, validation details, and MQTTX examples, see MQTT Access.

A2A over MQTT

A2A clients use MQTT v5 to discover and call A2A-enabled Device Agents on the live network:

PurposeTopic
Discover a card$a2a/v1/discovery/{org_id}/{unit_id}/{agent_id}
Discover within one organization$a2a/v1/discovery/{org_id}/+/+
Discover across organizations$a2a/v1/discovery/+/+/+
Send a request$a2a/v1/request/{org_id}/{unit_id}/{agent_id}
Receive repliesSet an MQTT v5 responseTopic, normally under $a2a/v1/reply/...

Agent cards are QoS 1 retained messages; an empty retained message removes a card. Device Agent handles JSON-RPC SendMessage requests. Publish requests at QoS 1 and include responseTopic; optional correlationData is returned unchanged. Use params.message.contextId to continue the same context and params.metadata.deviceId to target a device.

Request example:

json
{
  "jsonrpc": "2.0",
  "id": "task-001",
  "method": "SendMessage",
  "params": {
    "message": {
      "role": "user",
      "parts": [
        {
          "type": "text",
          "text": "Check the current temperature. If it is too high, switch the air conditioner to cooling mode."
        }
      ],
      "taskId": "task-001",
      "contextId": "server-room-session"
    },
    "metadata": {
      "sender": "my-a2a-client",
      "deviceId": "air-conditioner-01"
    }
  }
}

Replies use QoS 1 and are published to responseTopic:

JSON-RPC resultContent
result.statusUpdatetaskId, optional contextId, and status.state, status.timestamp, and optional status.message.
result.artifactUpdatetaskId, optional contextId, and artifact.artifactId, artifact.parts, append, and lastChunk.

Complete text arrives first in artifactUpdate.artifact.parts[].text, followed by TASK_STATE_COMPLETED; failures end with TASK_STATE_FAILED. Match the JSON-RPC id and correlationData, and set a client-side reply timeout.

See A2A Multi-Agent Collaboration for enabling agents, using Tasks and Scenes, and troubleshooting.

HTTP

HTTP API paths start with /api and currently have no version segment. /api/chat and the multi-agent orchestration execution endpoints return Server-Sent Events; other public integration endpoints use JSON. Check the release history before upgrading.

GET /api/health only shows that the HTTP process can respond. It does not check the LLM, MQTT, or device availability.

Chat and Vision

MethodPathPurpose
GET/api/healthCheck whether the HTTP API is available.
POST/api/chatStart text chat. Requires stream: true.
GET/api/sessions/:sessionId/historyRead session history.
POST/api/sessions/:sessionId/interruptInterrupt a session.
DELETE/api/sessions/:sessionIdClear a session.
POST/api/sessions/:sessionId/tool-approvalsGrant or revoke a high-risk tool approval. Trusted administration clients only.
POST/api/vision/framesUpload a vision frame for later chat use.
Request fieldRequirement
messageRequired non-empty text.
streamMust be true.
sessionIdTerminal clients should generate and retain it; history, interrupt, clear, and tool approval calls use the same ID.
metadata.productId, metadata.deviceIdInclude when device context is needed.
visionRefsOptional references to frames uploaded for the same session.

Chat example:

bash
$ curl -N http://127.0.0.1:3000/api/chat \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -d '{
    "message": "Check the current temperature and set the target temperature to 24",
    "stream": true,
    "sessionId": "demo-session",
    "metadata": {
      "productId": "thermostat",
      "deviceId": "thermostat-001"
    }
  }'

SSE events:

EventDataTerminal
statustype is start, thinking, text_block, tool_start, tool_end, complete, or error.No
message{ "text", "sessionId", "timestamp" }Yes
error{ "error", "sessionId", "timestamp?" }Yes

Only one chat request can be active per sessionId; another returns 429. A request waits for at most five minutes, then emits error and aborts the session. Disconnecting SSE also aborts the turn. To interrupt explicitly, call POST /api/sessions/:sessionId/interrupt.

Tool Approval

When a status event contains data.approvalRequired: true, the original SSE stream waits for approval. After verifying toolName and toolInput, a trusted administration client must submit this within five minutes:

json
{
  "action": "approve",
  "toolName": "write_file",
  "scope": "once",
  "target": {
    "type": "write_file",
    "path": "<toolInput.path>"
  }
}

toolName accepts write_file or execute_command; scope accepts once or session and defaults to once. An execute_command target is { "type": "execute_command", "command": "...", "cwd": "..." }. The API returns 403 when the server has not enabled the tool. Prefer a one-use approval with an exact target.

Upload a vision frame with /api/vision/frames, then pass the returned frameId and capturedAt to /api/chat as visionRefs. mimeType supports image/jpeg, image/png, and image/webp.

bash
$ curl http://127.0.0.1:3000/api/vision/frames \
  -H 'Content-Type: application/json' \
  -d '{
    "sessionId": "demo-session",
    "deviceId": "thermostat-001",
    "mimeType": "image/png",
    "imageBase64": "<base64>",
    "source": "camera"
  }'

A successful upload returns 201 with frameId, capturedAt, source, and mimeType; invalid input returns 400; an unavailable frame store returns 503. The imageBase64 string is limited to 6 MiB. Each session retains at most 20 frames for 5 minutes. Clearing the session also deletes its frames.

Pass the vision frame to chat:

json
{
  "message": "Use this image to check whether the device screen looks abnormal",
  "stream": true,
  "sessionId": "demo-session",
  "visionRefs": [
    {
      "frameId": "frame-001",
      "capturedAt": "2026-05-11T10:00:00.000Z",
      "source": "camera"
    }
  ]
}

Devices, Commands, and Events

MethodPathPurpose
GET/api/productsList device agents.
GET/api/products/:productIdRead a device agent definition, including properties, commands, parameters, and events.
GET/api/products/:productId/devicesList devices. Supports status and tags filters.
GET/api/products/:productId/devices/:deviceIdGet device details.
POST/api/products/:productId/devices/:deviceId/commandsSend a command to an online device.
GET/api/products/:productId/devices/:deviceId/eventsRead device events.

Read real IDs and the product definition first. Do not construct command names or parameters:

bash
$ export BASE=http://127.0.0.1:3000
$ curl "$BASE/api/products"
$ export PRODUCT_ID=your_product_id
$ curl "$BASE/api/products/$PRODUCT_ID"
$ curl "$BASE/api/products/$PRODUCT_ID/devices?status=online&tags=site:lab"
$ export DEVICE_ID=your_device_id

Device list entries include status, state, tags, modelStatus, and lastSeenAt. status accepts online, offline, error, or all. Multiple tags=key:value,key:value filters must all match.

Use a command and parameters from the product definition:

bash
$ curl "$BASE/api/products/$PRODUCT_ID/devices/$DEVICE_ID/commands" \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "set_drive_mode",
    "params": {
      "mode": "eco"
    },
    "timeoutMs": 30000
  }'

timeoutMs defaults to 30,000 and is limited to 120,000. Command responses:

HTTPResult
200{ "result": <device response> }
400COMMAND_VALIDATION_FAILED
404DEVICE_NOT_FOUND
409DEVICE_NOT_ONLINE or DEVICE_COMMAND_FAILED
504COMMAND_TIMEOUT

After 504, the device may still execute the command later. Check device state before retrying.

Device events are newest first. limit defaults to 50 and is capped at 200. For the next page, pass the last event's id as beforeId.

Multi-Agent Orchestration

The HTTP orchestration API is available only on the main HTTP port and creates or runs A2A Scenes and one-off Tasks. Configure a usable LLM, then select a real agentId from entries in /api/a2a/agents whose status is online; do not construct it. visibility accepts public, private, or all and defaults to all. When a local device is needed, use the card's top-level productId to query a real deviceId.

MethodPathPurpose
GET/api/a2a/agents?visibility=allList agent cards and online state in the current A2A organization and unit.
GET/api/a2a/scenesList saved Scenes.
POST/api/a2a/scenesCreate a Scene and generate its collaboration flow asynchronously.
GET/api/a2a/scenes/:sceneIdRead Scene details and generation status.
POST/api/a2a/scenes/:sceneId/inputAdd information to a Scene in needs_input.
POST/api/a2a/scenes/:sceneId/recreateReplace the Scene name, goal, and agents, then regenerate it.
DELETE/api/a2a/scenes/:sceneIdDelete a Scene.
POST/api/a2a/chatSelect one ready Scene for conversation or execution. Returns SSE.
POST/api/a2a/scenes/:sceneId/chatLimit the conversation to one Scene. Returns SSE.
POST/api/a2a/tasksStart a one-off multi-agent Task. Returns SSE.

Create a Scene and Wait Until It Is Ready

bash
$ curl "$BASE/api/a2a/scenes" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Server room response",
    "instructions": "Check server room temperature and humidity, adjust the air conditioner when the threshold is exceeded, and return an action report",
    "agentIds": ["<sensor-agent-id>", "<air-conditioner-agent-id>"]
  }'

The endpoint returns 202 { "sceneId": "<scene-id>", "status": "creating" }. Save sceneId and poll GET /api/a2a/scenes/:sceneId:

statusWhat to do
creatingKeep waiting and querying. Creation and regeneration both use this state.
needs_inputRead pendingInput, submit the missing information, and continue querying.
readyThe Scene can run.
failedRead failureReason, address the reported cause, and call recreate.

When more information is needed, submit { "answers": "..." } to /api/a2a/scenes/:sceneId/input. recreate takes the same name, instructions, and agentIds, keeps sceneId, and regenerates the Scene.

Run a Scene

The shared Scene conversation uses /api/a2a/chat. Name the Scene and state the execution intent. The planner selects at most one ready Scene:

bash
$ curl -N "$BASE/api/a2a/chat" \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -d '{
    "prompt": "Run the Server room response Scene now and return the final action report",
    "contextId": "terminal-demo-001"
  }'

When the caller has the exact sceneId, it can send the same prompt to /api/a2a/scenes/:sceneId/chat to limit the candidate set.

Both endpoints are conversational. If the request is not explicit enough, the system can answer or ask for clarification instead of dispatching device actions. The caller stores multi-turn Scene history and sends it with each request; contextId identifies the downstream A2A context and does not persist HTTP conversation history.

Start a One-Off Task

A one-off Task does not save a Scene. devices is optional. When preselecting devices, each productId can appear only once.

bash
$ curl -N "$BASE/api/a2a/tasks" \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -d '{
    "text": "Check every room temperature, set air conditioners to 24°C in rooms above 28°C, and return a summary",
    "devices": [
      {
        "productId": "<sensor-product-id>",
        "deviceId": "<sensor-device-id>"
      },
      {
        "productId": "<air-conditioner-product-id>",
        "deviceId": "<air-conditioner-device-id>"
      }
    ]
  }'

The first request must include text. A Task can also answer or ask for clarification and does not always dispatch device actions. Optional history contains at most 12 { "role": "user|assistant", "text": "..." } messages, with at most 4,000 characters in each text. The caller stores conversation history and sends it with later new Task requests.

Handle Device Selection

When execution needs a concrete device and no usable selection was supplied, the current SSE stream emits status: input-required, then ends with binding_required. appId is the Scene ID:

text
event: binding_required
data: {"kind":"scene","appId":"<scene-id>","continuationId":"<id>","prompt":"<prompt>","tasks":[{"taskId":"<task-id>","productId":"<product-id>"}]}

Send the continuation back to the original endpoint. Do not mix these body formats:

Original endpointContinuation body
/api/a2a/chatprompt, continuationId, and devices: [{ productId, deviceId }].
/api/a2a/scenes/:sceneId/chatprompt, continuationId, and deviceBindings: { taskId: deviceId }.
/api/a2a/tasksOnly continuationId and a non-empty devices; do not send text or history again.

continuationId remains valid for five minutes, can be used once, and is lost when the Gateway restarts. A Scene continuation is also invalidated when that Scene is deleted or regenerated. Select devices immediately after receiving the event.

Read SSE Events

Use curl -N to disable output buffering. Execution endpoints can emit these events:

SSE eventContent
statusworking, input-required, completed, or failed. For metadata.type: plan, planning text is in data.text; for task_step, step details are in data.metadata.
artifactFinal text appears in data.parts[].text.
binding_requiredReturns steps that need device bindings and a one-time continuationId, then ends the current stream.

HTTP 200 only means that the SSE stream was established. A run failure is reported as a status event with data.state: failed; a normal run ends with data.state: completed. Keep reading until a terminal event arrives.

Multi-agent execution is not a persisted job: the API does not return a run ID or provide run history or post-disconnect status queries. Disconnecting SSE stops steps that have not been dispatched, but does not undo actions already sent to agents or devices. Execution requests have no idempotency key; do not blindly repeat a request when its result is unknown.

See A2A Multi-Agent Collaboration for the console workflow.

Workflows

HTTP can list, enable, disable, and delete workflows. Workflow creation and full replacement are handled by Device Agent workflow tools, usually from a conversation that describes the trigger and handling steps. See Workflows for the usage flow.

MethodPathPurpose
GET/api/workflowsList saved workflows.
PATCH/api/workflows/:workflowIdEnable or disable a workflow. Body: { "enabled": true } or { "enabled": false }.
DELETE/api/workflows/:workflowIdDelete a workflow.
GET/api/workflow-runsList workflow runs. Supports limit, offset, and q.
GET/api/workflows/:workflowId/runsList runs for one workflow.
GET/api/workflow-runs/:runIdRead one run and its step results.

The list returns { "workflows": [...] }; PATCH returns { "workflow": {...} }; DELETE returns 204. Run lists return runs, total, limit, and offset; when q is provided, they also return query. limit defaults to 50 and is capped at 100.

Scheduled Tasks

Scheduled tasks are created in a Device Agent conversation. HTTP can list, pause, resume, update, cancel, and inspect their runs.

MethodPathPurpose
GET/api/timers?status=active&limit=50List tasks by active, paused, running, completed, or deleted status.
PATCH/api/timers/:timerIdSet enabled and/or replace instruction.
DELETE/api/timers/:timerIdCancel a task.
GET/api/timers/:timerId/runs?limit=25&offset=0List persisted runs for one task.

There is no HTTP create endpoint in 0.4.0. Changing the schedule rule also requires canceling the task and creating a new one in conversation.

The task list returns { "timers": [...] }, defaults to active, and caps limit at 100. PATCH returns { "timer": {...} }; DELETE returns 204. Run history returns { "runs", "total", "limit", "offset" }.

See Scheduled Tasks for creation and management in the console.

Webhook

MethodPathPurpose
GET/api/webhooksList saved connections without returning URLs, headers, or signing secrets.
POST/api/webhooksCreate a connection.
PATCH/api/webhooks/:webhookIdUpdate its name, enabled state, credential, body template, or response condition.
DELETE/api/webhooks/:webhookIdDelete an unused connection. Returns 409 when a workflow still references it.
POST/api/webhooks/:webhookId/testSend a test message and wait for the delivery result.

Create a basic custom connection:

bash
$ curl "$BASE/api/webhooks" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "operations-alerts",
    "enabled": true,
    "credential": {
      "url": "https://hooks.example.com/device-agent"
    },
    "preset": "custom",
    "bodyTemplate": {
      "message": "{{message}}"
    }
  }'

Test the connection with POST /api/webhooks/:webhookId/test and { "message": "Device Agent test" }.

JSON request bodies are limited to 64 KiB. Create returns 201 { "webhook": {...} }; PATCH returns 200 { "webhook": {...} }; DELETE returns 204; a successful test returns 200 { "ok": true, "httpStatus": <status> }.

See Webhook for presets, signing, templates, delivery limits, and workflow use.

WebSocket

The voice channel uses /ws/voice. The connection can include these headers:

HeaderNotes
Protocol-VersionProtocol version. Current value: 3.
Device-IdCurrent device ID.
Client-IdClient ID. Defaults to device ID when omitted.

After connecting, send hello:

json
{
  "type": "hello",
  "version": 3,
  "audio_params": {
    "format": "pcm",
    "sample_rate": 16000,
    "channels": 1
  },
  "sessionId": "demo-session",
  "productId": "thermostat",
  "deviceId": "thermostat-001",
  "provider": "aliyun"
}

JSON control messages can use WebSocket text frames or type 1 binary frames. Audio uses type 0 binary frames in the format declared by audio_params. Every binary frame has a four-byte header:

ByteContent
0Type: 0 audio, 1 UTF-8 JSON.
1Reserved; set to 0.
2..3Payload length as a big-endian uint16.

Keep 16-bit PCM payloads even-sized and at or below 65,534 bytes. Generate a taskId for each turn and reuse it in listen, stop, and abort.

A voice turn usually follows this message flow:

DirectionMessageNotes
Client -> Device AgenthelloStart a voice session with audio parameters, device context, and speech provider.
Device Agent -> clienthelloReturn session ID and server audio output parameters.
Client -> Device AgentlistenSend `mode: auto
Client -> Device AgentBinary audio framesSend voice data.
Device Agent -> clientasrReturn text, definite, utterances, and taskId.
Client -> Device AgentstopSend the same taskId; may include visionRefs.
Device Agent -> clientagent_replyReturn text and taskId; streaming: true marks incremental text.
Device Agent -> clientTTS binary audio framesPlay synthesized voice reply.
Device Agent -> clienttts_completeEnd the completed or aborted turn; may include taskId.
Client -> Device AgentabortInterrupt the turn with its taskId.
Client -> Device AgentgoodbyeInclude the session_id returned by server hello.

Server hello.audio_params defines the downstream TTS audio format; play PCM using its sample_rate. Even with listen.mode: auto, send stop at the end of each recording turn. Close code 1000 is normal; 1011 means the voice service is unavailable, and 1012 means voice configuration was reloaded and the client may reconnect.

See Voice Configuration for settings and Voice Interaction for usage.