API: Digital Humans
Halo HTTP API for Digital Humans: install and uninstall, pause and resume, configure, read run history and activity, and chat with them.
Digital Humans (app in the API) start work on a schedule, on events, or when someone messages them. Use these operations to install and remove them, pause and resume them, change their configuration, read their run history and activity, and chat with them.
Paths are relative to your Halo base URL, and every request carries the access password. Set $HALO_URL and $HALO_TOKEN as in the quickstart; base URL, authentication and error conventions are in the HTTP API overview.
List digital humans (and other installed apps) in a space
GET /api/apps Assistant
- Optional query parameters:
spaceId(only that workspace) andstatus. Withoutstatus, uninstalled Digital Humans are left out. WithoutspaceId, the list covers every workspace, as the Halo app shows it. userConfigand theenvandheadersof an MCP app'sspec.mcp_servercan hold credentials in plain text, and this API returns them as stored. Treat the response like a secret.
Query parameters: spaceId, status
Response
{success:true,data:[{id,specId,spaceId,spec,status,userConfig,userOverrides,permissions,installedAt,lastRunAt?,lastRunOutcome?,errorMessage?}]}Example
curl -s "$HALO_URL/api/apps" \
-H "Authorization: Bearer $HALO_TOKEN"Get one installed digital human (or other app) by id
GET /api/apps/:appId Assistant
userConfigand theenvandheadersof an MCP app'sspec.mcp_servercan hold credentials in plain text, and this API returns them as stored. Treat the response like a secret.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:InstalledApp|null} // same shape as the entries of GET /api/appsExample
curl -s "$HALO_URL/api/apps/<appId>" \
-H "Authorization: Bearer $HALO_TOKEN"Soft-delete (uninstall) a digital human; recoverable via reinstall
DELETE /api/apps/:appId Reversible Assistant
- Uninstalls the Digital Human but keeps its data, so
POST /api/apps/:appId/reinstallbrings it back. Thepurge=truequery parameter is accepted but currently has no effect. - To delete it for good, call
DELETE /api/apps/:appId/permanentafterwards.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Query parameters: purge
Response
{success:true}Example
curl -sX DELETE "$HALO_URL/api/apps/<appId>" \
-H "Authorization: Bearer $HALO_TOKEN"Get the activity/run-history entries for a digital human
GET /api/apps/:appId/activity Assistant
- Optional query parameters:
limit(most entries),before(only entries older than this time, epoch ms),beforeId,offset,type(run_complete,run_skipped,run_error,milestone,escalationoroutput), andteamIdwithepochId(entries of one team run).
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Query parameters: before, beforeId, epochId, limit, offset, teamId, type
Response
{success:true,data:[{id,appId,runId,type:"run_complete"|"run_skipped"|"run_error"|"milestone"|"escalation"|"output",ts,content,userResponse?}]}Example
curl -s "$HALO_URL/api/apps/<appId>/activity" \
-H "Authorization: Bearer $HALO_TOKEN"List skills this digital human can load (global + space-local)
GET /api/apps/:appId/available-skills Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:[{name,description,scope:"global"|"space",dirName,path}]}Errors
- 404-equivalent = {success:false,error:"App not found or has no space"} — the app must have a spaceId
Example
curl -s "$HALO_URL/api/apps/<appId>/available-skills" \
-H "Authorization: Bearer $HALO_TOKEN"Clear a digital human's chat history
POST /api/apps/:appId/chat/clear Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- spaceId is required even though the app is already scoped to one. Optional conversationId clears one specific session instead of the default one.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"spaceId": "<spaceId — the app's own spaceId from GET /api/apps>"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/chat/clear" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId — the app'\''s own spaceId from GET /api/apps>"}'Read a digital human chat transcript
GET /api/apps/:appId/chat/messages Assistant
- Reads the main thread by default. For another thread pass
?conversationId=, the same keyPOST /api/apps/:appId/chat/sendtakes. - An empty list means the thread has no history yet, or the id is wrong — the two look the same. List the threads with
GET /api/im-sessionsrather than guessing. thoughtsholds the raw tool calls and results behind each reply and is large.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Query parameters: conversationId
Response
{success:true,data:[{id,role:"user"|"assistant",content,timestamp,thoughts?,thoughtsSummary?,images?}]}Example
curl -s "$HALO_URL/api/apps/<appId>/chat/messages" \
-H "Authorization: Bearer $HALO_TOKEN"Restart a digital human's chat agent process (reloads system prompt and config)
POST /api/apps/:appId/chat/restart Assistant
- Interrupts any in-flight turn. Conversation history is preserved.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{sessionsClosed:number}}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/chat/restart" \
-H "Authorization: Bearer $HALO_TOKEN"Send a message to a digital human and let it reply
POST /api/apps/:appId/chat/send Assistant
spaceIdis the Digital Human's own workspace: thespaceIdof its entry inGET /api/apps.conversationIdpicks the thread. Leave it out for the main thread. For a new thread, use theconversationIdthatPOST /api/apps/:appId/sessions/createreturns. To continue a thread the user started in Halo, build it fromGET /api/im-sessionsasapp-chat:<appId>:<channel>:<chatType>:<chatId>.- Only threads on the
localandhttpchannels are accepted. A thread that belongs to an IM chat is refused, so this API never speaks into a real conversation with a person. A malformed id returns 400 with the expected form;chatIdmay contain only letters, digits,_and-. - Returns before the Digital Human has read the message. Follow the reply over the WebSocket, or poll
GET /api/apps/:appId/chat/statusuntilisGeneratingis false and then readGET /api/apps/:appId/chat/messages, passing the sameconversationIdto both. - A message sent while the same thread is still answering doesn't start a second session: it joins the running turn after its current step, or is merged into the next one. Send one message at a time and wait for the reply, so each reply answers one message.
- Optional body fields:
thinkingEnabled,reasoningEffort(offtomax; overridesthinkingEnabled, but the Digital Human's own setting wins),images.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"spaceId": "<spaceId>",
"message": "What did you find today?"
}Response
{success:true,data:{conversationId}} // accepted, not answeredExample
curl -sX POST "$HALO_URL/api/apps/<appId>/chat/send" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId>","message":"What did you find today?"}'Check whether a digital human chat is currently generating a reply
GET /api/apps/:appId/chat/status Assistant
- Checks the main thread by default; pass
?conversationId=for another thread, the same keyPOST /api/apps/:appId/chat/sendtakes. - Poll with a growing interval (2 s, then double) instead of a fixed wait.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Query parameters: conversationId
Response
{success:true,data:{isGenerating:boolean,conversationId}}Example
curl -s "$HALO_URL/api/apps/<appId>/chat/status" \
-H "Authorization: Bearer $HALO_TOKEN"Stop a digital human's in-progress chat reply
POST /api/apps/:appId/chat/stop Assistant
- Name the thread to stop with
conversationId. To stop every thread of this Digital Human, send{"all": true}instead. Sending neither returns 400.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"conversationId": "<conversationId>"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/chat/stop" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"conversationId":"<conversationId>"}'Clear the history of all of a digital human's chats at once
POST /api/apps/:appId/chats/clear-all Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- Each chat is cleared as /clear clears it and starts afresh with its next message; replies in progress stop. API sessions, team chats, memory and reminders are left alone. Nothing is posted into the chats.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{cleared:number,failed:number}}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/chats/clear-all" \
-H "Authorization: Bearer $HALO_TOKEN"Count the conversations clearing all of a digital human's chats would clear
GET /api/apps/:appId/chats/clearable Assistant
- total: its default and local chats and its IM chats that have history (im of them in IM); API sessions and team chats are not counted. teamIm: IM chats with history that a team answers — a clear-all leaves them alone; /clear in each clears it.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{total:number,im:number,teamIm:number}}Example
curl -s "$HALO_URL/api/apps/<appId>/chats/clearable" \
-H "Authorization: Bearer $HALO_TOKEN"Delete all of a digital human's memory files
POST /api/apps/:appId/clear-memory Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{filesRemoved:number}}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/clear-memory" \
-H "Authorization: Bearer $HALO_TOKEN"Replace a digital human's user configuration values
POST /api/apps/:appId/config Reversible Assistant
- Body is the full config object, keyed by config_schema field id. This replaces userConfig wholesale; read current values first with GET /:appId to avoid dropping fields.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"someField": "value"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/config" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"someField":"value"}'Answer a digital human's pending question (it stopped to ask a human before continuing)
POST /api/apps/:appId/escalation/:entryId/respond Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- Answers a question a Digital Human asked during a run (an activity entry of type
escalation; the Digital Human's status iswaiting_userwhile it waits). Sendchoice,text, or both, as the question asked for; for a question with several parts, sendanswers, one{choice?, text?}per part, in order. - This is the human decision the run was waiting for. Send the user's actual answer: it cannot be withdrawn afterwards.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
entryId | Activity entry id, from GET /api/apps/:appId/activity. |
Request body
{
"choice": "approve",
"text": "optional free-text explanation"
}Response
{success:true,data:{…the answered activity entry}} | {success:false,error}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/escalation/<entryId>/respond" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"choice":"approve","text":"optional free-text explanation"}'Change how often a digital human runs, without editing the rest of its definition (spec)
POST /api/apps/:appId/frequency Reversible Assistant
- frequency can also be a cron expression (e.g. "0 8 * * *"); either one replaces the other. subscriptionId must name an existing schedule subscription — read app.spec.subscriptions via GET /:appId first. This rewrites that subscription's schedule in the spec, which is the only interval the scheduler reads and the one the settings panel shows; the rest of the spec is untouched.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"subscriptionId": "<subscriptionId — read app.spec.subscriptions via GET /api/apps/<appId>>",
"frequency": "30m"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/frequency" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subscriptionId":"<subscriptionId — read app.spec.subscriptions via GET /api/apps/<appId>>","frequency":"30m"}'Clear a digital human's chat history in a bound IM chat
POST /api/apps/:appId/im-chat/clear Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- All four body fields are required, 400 otherwise. channel, chatType and chatId are checked as for GET im-chat/messages; chatType is "direct" or "group".
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"spaceId": "<spaceId — the app's own spaceId from GET /api/apps>",
"channel": "wecom-bot",
"chatType": "direct",
"chatId": "<chatId — from GET /api/im-sessions>"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/im-chat/clear" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId — the app'\''s own spaceId from GET /api/apps>","channel":"wecom-bot","chatType":"direct","chatId":"<chatId — from GET /api/im-sessions>"}'Read a digital human's transcript in a bound IM chat
GET /api/apps/:appId/im-chat/messages Assistant
- All three query params are required, 400 otherwise. spaceId must be the app's own — a different but valid one is accepted and reads the wrong space. Optional ?chatType=group, defaults to direct.
- channel is the session's IM channel (wecom-bot, feishu-bot, dingtalk-bot, weixin-ilink-bot); chatId is taken as
GET /api/im-sessionsgives it. Any other channel, chatType or a chatId with whitespace, ":", "/", "" or ".." is a 400. Read "http" and "local" sessions withGET /api/apps/:appId/chat/messages?conversationId=. - This is real correspondence with a person on the other end — read it only when the task needs it.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Query parameters: channel (required), chatId (required), spaceId (required), chatType
Response
{success:true,data:[{id,role:"user"|"assistant",content,timestamp,thoughts?,thoughtsSummary?,images?}]}Example
curl -s "$HALO_URL/api/apps/<appId>/im-chat/messages?channel=wecom-bot&chatId=<chatId — from GET /api/im-sessions>&spaceId=<spaceId — the app's own spaceId from GET /api/apps>" \
-H "Authorization: Bearer $HALO_TOKEN"Stop a digital human's in-progress reply in a bound IM chat (keeps history)
POST /api/apps/:appId/im-chat/stop Assistant
- channel, chatType and chatId are checked as for GET im-chat/messages; chatType is "direct" or "group". Unlike im-chat/clear, this only aborts the current turn — the session and its history continue on the next inbound message.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"channel": "wecom-bot",
"chatType": "direct",
"chatId": "<chatId — from GET /api/im-sessions>"
}Response
{success:true,data:{stopped:boolean}}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/im-chat/stop" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel":"wecom-bot","chatType":"direct","chatId":"<chatId — from GET /api/im-sessions>"}'Read the state of a digital human's memory
GET /api/apps/:appId/memory Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{exists,totalBytes,topicCount,lastConsolidatedAt,lastAttempt,consolidating}}Example
curl -s "$HALO_URL/api/apps/<appId>/memory" \
-H "Authorization: Bearer $HALO_TOKEN"Consolidate a digital human's memory now
POST /api/apps/:appId/memory/consolidate Reversible Assistant
- Returns once started; runs in the background with the digital human's model. Keeps a restorable snapshot.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{started:boolean,reason?:"already-running"|"empty"|"not-found"}}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/memory/consolidate" \
-H "Authorization: Bearer $HALO_TOKEN"Set which space a digital human starts new work in
POST /api/apps/:appId/move-space Reversible Assistant
- Changes the default only: work already running, existing chat sessions and team work keep the environment they started in. A digital human must belong to a space, so newSpaceId is required — null is rejected. Nothing is stopped or restarted. 404 if appId does not exist; 400 if newSpaceId is an empty string.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"newSpaceId": "<spaceId — a uuid from GET /api/spaces>"
}Response
{success:true,data:{}}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/move-space" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"newSpaceId":"<spaceId — a uuid from GET /api/spaces>"}'Merge-patch per-installation overrides (notification level, model)
PATCH /api/apps/:appId/overrides Reversible Assistant
- JSON Merge Patch: send only the fields to change;
nullclears one (for example{"modelSourceId":null}falls back to the global model). - Fields:
notificationLevel,modelSourceId,modelId,spaceMemoryAccess(true lets the Digital Human read its workspace's memory topics),chatReasoningEffort(offtomax, for its chats, IM replies and runs),memory({enabled, autoConsolidate, cadence}, always the whole object;cadenceisdiligent,balancedoreconomical) anddisabledTools(MCP apps only: tool names to leave out, the whole list each time;[]ornullturns them all back on). - A
modelSourceIdfor an account that no longer exists is accepted; the Digital Human then runs on the current global account until the override changes. - The run schedule is not set here; use
POST /api/apps/:appId/frequency.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"notificationLevel": "important"
}Response
{success:true}Example
curl -sX PATCH "$HALO_URL/api/apps/<appId>/overrides" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"notificationLevel":"important"}'Pause a digital human (stops scheduling)
POST /api/apps/:appId/pause Reversible Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true}Errors
- 404-equivalent: manager.pause throws for an unknown appId
Example
curl -sX POST "$HALO_URL/api/apps/<appId>/pause" \
-H "Authorization: Bearer $HALO_TOKEN"Permanently delete a digital human and all its data
DELETE /api/apps/:appId/permanent Irreversible
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it. If you only need something narrower, use DELETE /api/apps/:appId
- Deletes the Digital Human and all its data. It must be uninstalled first with
DELETE /api/apps/:appId; otherwise the call fails. - Built-in Digital Humans cannot be deleted. Failures carry
errorNameso a client can tell the cases apart.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true} | {success:false,error,errorName}Example
curl -sX DELETE "$HALO_URL/api/apps/<appId>/permanent" \
-H "Authorization: Bearer $HALO_TOKEN"Grant a named permission to a digital human
POST /api/apps/:appId/permissions/grant Reversible Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"permission": "ai-browser"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/permissions/grant" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"permission":"ai-browser"}'Revoke a named permission from a digital human
POST /api/apps/:appId/permissions/revoke Reversible Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"permission": "ai-browser"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/permissions/revoke" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"permission":"ai-browser"}'Reinstall a previously soft-deleted (uninstalled) digital human
POST /api/apps/:appId/reinstall Reversible Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{activationWarning?:string}} — activationWarning is set (non-fatal) if restarting it failedErrors
- 404-equivalent: manager.reinstall throws if appId was never installed or is not currently uninstalled
Example
curl -sX POST "$HALO_URL/api/apps/<appId>/reinstall" \
-H "Authorization: Bearer $HALO_TOKEN"List the reminders a digital human set in its conversations
GET /api/apps/:appId/reminders Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:[{id,conversationId,conversation:{kind,name?},message,schedule,nextAt,createdAt,setBy?}]}Example
curl -s "$HALO_URL/api/apps/<appId>/reminders" \
-H "Authorization: Bearer $HALO_TOKEN"Cancel a reminder a digital human set in one of its conversations
DELETE /api/apps/:appId/reminders/:reminderId Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
reminderId | Reminder id, from GET /api/apps/:appId/reminders. |
Response
{success:true} | {success:false,error:"Reminder not found"}Example
curl -sX DELETE "$HALO_URL/api/apps/<appId>/reminders/<reminderId>" \
-H "Authorization: Bearer $HALO_TOKEN"Resume a paused digital human
POST /api/apps/:appId/resume Reversible Assistant
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true}Errors
- 404-equivalent: manager.resume throws for an unknown appId
Example
curl -sX POST "$HALO_URL/api/apps/<appId>/resume" \
-H "Authorization: Bearer $HALO_TOKEN"Resume a run that stopped prematurely on an error
POST /api/apps/:appId/runs/:runId/continue Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- Only valid for a run that ended in a premature-stop error, not any run. This re-opens the run with full tool permissions (bypassPermissions) and can take real-world action that Halo cannot undo.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
runId | Id of one Digital Human run, the runId in GET /api/apps/:appId/activity. |
Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/runs/<runId>/continue" \
-H "Authorization: Bearer $HALO_TOKEN"Inject a text message into a currently active run
POST /api/apps/:appId/runs/:runId/inject Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- The message reaches the running Digital Human, which keeps its full tool permissions and can act on it in ways Halo cannot undo.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
runId | Id of one Digital Human run, the runId in GET /api/apps/:appId/activity. |
Request body
{
"text": "Also check the March invoices"
}Response
{success:true}Errors
- 400 when
textis missing or the run is not active.
Example
curl -sX POST "$HALO_URL/api/apps/<appId>/runs/<runId>/inject" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"Also check the March invoices"}'Read what a run actually did, step by step
GET /api/apps/:appId/runs/:runId/session Assistant
- The transcript behind the "View process" panel — same shape as chat/messages, but for one automation run. It is the only way to see why a run did what it did rather than what it reported.
- Get the runId from
GET /api/apps/<appId>/activity. Read this when a run went wrong; for what it concluded, its activity entry is shorter and enough. - Only a person's newest 200 runs keep their transcript. An older run answers {success:false,code:"RUN_PROCESS_CLEARED"}; its activity entry still says what it did, and it can no longer be continued.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
runId | Id of one Digital Human run, the runId in GET /api/apps/:appId/activity. |
Response
{success:true,data:[{id,role:"user"|"assistant",content,timestamp,thoughts?,thoughtsSummary?,images?}]}Errors
- 404 when the app or its space is gone. An empty array means that run left no transcript, not that the ids were wrong.
Example
curl -s "$HALO_URL/api/apps/<appId>/runs/<runId>/session" \
-H "Authorization: Bearer $HALO_TOKEN"Start a new chat thread with a digital human
POST /api/apps/:appId/sessions/create Assistant
- Returns a new thread's
conversationId, ready to pass toPOST /api/apps/:appId/chat/sendandGET /api/apps/:appId/chat/messages. Give each end user a thread of their own to keep their contexts apart.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{conversationId}} // already in the form chat/send takesExample
curl -sX POST "$HALO_URL/api/apps/<appId>/sessions/create" \
-H "Authorization: Bearer $HALO_TOKEN"Delete a chat thread with a digital human
POST /api/apps/:appId/sessions/delete Irreversible Assistant
Irreversible
This cannot be undone, and nothing goes to a trash folder. Make sure it is what the user wants before you call it.
- The id names one of this digital human's own threads, never a conversation from
GET /api/spaces/<spaceId>/conversations.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"spaceId": "<spaceId — the app's own spaceId from GET /api/apps>",
"conversationId": "<conversationId — a thread of THIS app, from GET /api/im-sessions?appId=>"
}Response
{success:true}Errors
- 400 if spaceId or conversationId is missing.
Example
curl -sX POST "$HALO_URL/api/apps/<appId>/sessions/delete" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId — the app'\''s own spaceId from GET /api/apps>","conversationId":"<conversationId — a thread of THIS app, from GET /api/im-sessions?appId=>"}'Branch an existing chat thread into a new one, copying its history
POST /api/apps/:appId/sessions/fork Assistant
- A digital human's threads are not the conversations in a space — do not take an id from
GET /api/spaces/<spaceId>/conversations, it belongs to the user's chats with the Halo assistant and this route will not find it.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"spaceId": "<spaceId — the app's own spaceId from GET /api/apps>",
"sourceConversationId": "<conversationId — a thread of THIS app, from POST /api/apps/<appId>/sessions/create or assembled from GET /api/im-sessions?appId=>"
}Response
{success:true,data:{conversationId}}Errors
- 400 if spaceId or sourceConversationId is missing.
Example
curl -sX POST "$HALO_URL/api/apps/<appId>/sessions/fork" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId — the app'\''s own spaceId from GET /api/apps>","sourceConversationId":"<conversationId — a thread of THIS app, from POST /api/apps/<appId>/sessions/create or assembled from GET /api/im-sessions?appId=>"}'Edit part of a digital human's definition (spec) without replacing all of it
PATCH /api/apps/:appId/spec Reversible
- JSON Merge Patch on the top-level spec fields: each field you send replaces the stored one,
nullremoves it, and the merged spec is validated again. - A changed
subscriptionstakes effect without interrupting a run in progress. To change only how often it runs,POST /api/apps/:appId/frequencyis simpler. - A build can refuse remote changes that touch MCP apps (403).
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"description": "Checks the product page every weekday at 9:00"
}Response
{success:true} | {success:false,error}Example
curl -sX PATCH "$HALO_URL/api/apps/<appId>/spec" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"description":"Checks the product page every weekday at 9:00"}'Get real-time run state of a digital human (running/queued/idle/etc.)
GET /api/apps/:appId/state Assistant
- More detail than the "status" field on GET /:appId alone (adds nextRunAtMs, queued/idle distinction)
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{status:"running"|"queued"|"idle"|"paused"|"waiting_user"|"error",nextRunAtMs?,runningRunId?,lastRunAtMs?,lastStatus?,lastError?,consecutiveErrors?,pendingEscalationId?}}Example
curl -s "$HALO_URL/api/apps/<appId>/state" \
-H "Authorization: Bearer $HALO_TOKEN"Manually trigger a digital human to run immediately
POST /api/apps/:appId/trigger
- Runs the Digital Human now and answers only after the run has finished, which can take minutes. Use a long client timeout, or follow the run with
GET /api/apps/:appId/stateand the WebSocket instead of waiting on this call. - If a run is already in progress or queued, the call fails with that message.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Response
{success:true,data:{appId,runId,sessionKey,outcome:"useful"|"noop"|"error"|"skipped",startedAt,finishedAt,durationMs,tokensUsed?,errorMessage?,finalText?}} | {success:false,error}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/trigger" \
-H "Authorization: Bearer $HALO_TOKEN"Set how a store-installed digital human handles version upgrades
POST /api/apps/:appId/upgrade-strategy Reversible Assistant
- strategy is one of "auto" (silent patch/minor, notify on major), "notify" (always notify), or "manual" (no automatic checks)
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"strategy": "auto"
}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/apps/<appId>/upgrade-strategy" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"strategy":"auto"}'Install a digital human (or other app type) from a YAML definition (spec) string
POST /api/apps/import-spec
- Installs a Digital Human (or another app type) from the text of a
spec.yaml, validated the same way as in the app. OptionaluserConfigfills its configuration fields. - Unlike installing in the Halo app or through its assistant, this call does not install the skills the spec requires and does not undo a half-finished install.
- A build can refuse remote installs of MCP apps (403).
Request body
{
"spaceId": "<spaceId>",
"yamlContent": "<contents of spec.yaml>"
}Response
{success:true,data:{appId}} | {success:false,error,code}Errors
- 400 when
spaceIdoryamlContentis missing or invalid; other failures carry acode.
Example
curl -sX POST "$HALO_URL/api/apps/import-spec" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId>","yamlContent":"<contents of spec.yaml>"}'Install a digital human (or other app type) from its full definition (spec) object
POST /api/apps/install
- Installs a Digital Human (or a skill or MCP app) from a full spec object, the parsed form of a
spec.yaml.spaceId: nullinstalls a skill or MCP app for every workspace.userConfigis optional. - Unlike installing in the Halo app or through its assistant, this call does not install the skills the spec requires and does not undo a half-finished install. Check
GET /api/apps/:appIdafterwards.
Request body
{"spaceId": "<spaceId> | null", "spec": {…the parsed spec.yaml…}, "userConfig": {…}}Response
{success:true,data:{appId}} | {success:false,error,code?}Errors
- 400 missing
specor aspaceIdthat is neither a string nor null, 403MCP_COMMAND_BLOCKED(an MCP command this build's policy blocks), 409ALREADY_INSTALLED, 503NOT_INITIALIZED.
Example
curl -sX POST "$HALO_URL/api/apps/install" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId>","spec":{"name":"Price watcher","version":"1.0.0","author":"example","description":"Checks a product page every morning","type":"automation","system_prompt":"Check the product page and report price changes."}}'