Quickstart: call the Halo API
Quickstart for the Halo API: turn on Remote Access, get the address and access password, make your first curl request, and stream the reply over WebSocket.
This page takes you from turning on Remote Access to having Halo work in a new conversation through curl, with its reply streaming into your terminal.
Before you start
- A computer with Halo installed and an AI model set up, or a running Halo Server.
- A terminal with
curl. - Node.js 22 or later for the last steps (it ships a WebSocket client).
1. Turn on Remote Access
Halo desktop offers the API only while Remote Access is on:
- Open Settings › Remote Access and turn on Enable Remote Access.
- Copy the Local Address, such as
http://localhost:3456. To call from another device, copy the LAN Address instead. - Copy the Access Password.
On Halo Server, skip this step: the address is http://<HOST>:8080 by default, and the access password is the value of the HALO_REMOTE_PASSWORD environment variable.
The access password is an admin password
Whoever has the access password has full control of this Halo. Use it only in your own terminal and on your servers; never share it or commit it to a repository.
2. Keep the address and password
Store the address and password in shell variables; every following command uses them:
export HALO_URL='http://localhost:3456'
export HALO_TOKEN='<ACCESS_PASSWORD>'Replace <ACCESS_PASSWORD> with the password you copied. Use single quotes: a generated password can contain $, ! or &, which the shell would rewrite inside double quotes.
3. Check that the service is up
This endpoint needs no password:
curl -s "$HALO_URL/api/remote/status"Output:
{"success":true,"data":{"active":true,"clients":0,"version":"1.0.0"}}Now list your workspaces with the password:
curl -s "$HALO_URL/api/spaces" -H "Authorization: Bearer $HALO_TOKEN"Output (shortened):
{"success":true,"data":[{"id":"<SPACE_ID>","name":"…","path":"…"}]}Note the id of a workspace. To use the Halo Workspace, its id is always halo-temp. A 401 means the password is wrong; check step 2.
4. Create a conversation
curl -sX POST "$HALO_URL/api/spaces/<SPACE_ID>/conversations" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"From my script"}'Replace <SPACE_ID> with the workspace id from step 3. Output (shortened):
{"success":true,"data":{"id":"<CONVERSATION_ID>","title":"From my script"}}Note the conversation's id. The conversation also shows up in Halo's conversation list.
5. Get ready to stream the reply
Save the script below as watch.mjs. It signs in to the WebSocket with the access password, subscribes to the conversation and prints the reply as it is written:
const conversationId = process.argv[2]
const ws = new WebSocket(process.env.HALO_URL.replace(/^http/, 'ws') + '/ws')
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'auth', payload: { token: process.env.HALO_TOKEN } }))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'auth:failed') {
console.error('Access password rejected')
process.exit(1)
}
if (msg.type === 'auth:success') {
ws.send(JSON.stringify({ type: 'subscribe', payload: { conversationId } }))
console.log('Waiting for Halo…')
return
}
if (msg.type !== 'event' || msg.data.conversationId !== conversationId) return
if (msg.channel === 'agent:message' && typeof msg.data.delta === 'string') {
process.stdout.write(msg.data.delta)
}
if (msg.channel === 'agent:complete') {
console.log('\n[done]')
ws.close()
}
if (msg.channel === 'agent:error') {
console.error('\n[error]', msg.data.error)
ws.close()
}
}Run it in the same terminal and leave it waiting:
node watch.mjs <CONVERSATION_ID>6. Give Halo some work
Open a second terminal, run the two export lines from step 2 again, then send a message:
curl -sX POST "$HALO_URL/api/agent/message" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<SPACE_ID>","conversationId":"<CONVERSATION_ID>","message":"List the files in this workspace and summarize them in three sentences"}'Output:
{"success":true}success: true means the work has started; the reply is written afterwards. Switch back to the first terminal and watch it appear.
Done
When the first terminal prints Halo's reply and ends with [done], you have had Halo finish a task through the API. Open the conversation in Halo and you see the same messages.
Without the WebSocket
If you only need the final result, poll instead:
- Call
GET /api/agent/generating/<CONVERSATION_ID>every few seconds, doubling the interval each time (2 s, 4 s, …), until it returns"data": false. - Read the conversation with
GET /api/spaces/<SPACE_ID>/conversations/<CONVERSATION_ID>. The last message whoseroleisassistantis the reply.
Next steps
- HTTP API overview: base URL, authentication, errors and every endpoint group
- WebSocket events: every event channel and the rules for assembling a reply
- API: Digital Humans: have Digital Humans work for your users