API: Teams
Halo HTTP API for teams of Digital Humans: create and dissolve teams, manage members, start and stop runs, set triggers, and read boards and outputs.
A team is a group of Digital Humans working toward one goal. Use these operations to create and dissolve teams, manage members and how they work together, start and stop runs, set triggers, and read boards, conversations and the files a run produced.
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 teams, optionally only those owned by one space
GET /api/teams Assistant
- Optional query parameter
spaceIdnarrows the list to teams owned by one workspace; without it the list covers every workspace, as the Halo app shows it. ephemeral: truemarks a temporary collaboration started from a conversation. It stays off the Teams page until it is kept withPOST /api/teams/:teamId/save-collab.hostNodeIdset means the team is hosted on someone else's computer and was joined from here: you can read it and message its members, but not edit or run it.
Query parameters: spaceId
Response
{success:true,data:[{id,name,status:"idle"|"running"|"waiting_user"|"error",memberCount,hasWaitingUser,waitingCount?,leadAppId,localMembers:[{appId,memberName,isLead}],hostNodeId?,ephemeral?,updatedAt}]}Example
curl -s "$HALO_URL/api/teams" \
-H "Authorization: Bearer $HALO_TOKEN"Create a persistent team, with a lead provisioned for it
POST /api/teams Reversible Assistant
- memberSourcing "manual": members are existing digital humans (appId from
GET /api/apps). memberSourcing "ai": omit members and send "confirmedProposal": [{memberName, role, responsibility}] (up to 5) beside "input" —POST /api/teams/propose-membersdrafts one; Halo creates a new digital human for each. - collabMode: "free" (members contact anyone) or "structured" (edges are generated from the lead; edit them with
PUT /api/teams/:teamId/edges). escalationRouting: "user" (questions go to the user) or "lead". - Creates digital humans (the lead, and AI-sourced members) as a side effect; on any failure the whole team is rolled back.
- Create a persistent team only when the user asked for one. Members' delegated permissions are set in the Halo app — see this page's withheld list.
Request body
{
"input": {
"name": "Weekly market brief",
"goal": "Every Monday, summarise competitor moves into brief.md",
"owningSpaceId": "<spaceId — a uuid from GET /api/spaces>",
"memberSourcing": "manual",
"collabMode": "free",
"escalationRouting": "user",
"members": [
{
"appId": "<appId — a digital human from GET /api/apps>",
"memberName": "Researcher",
"role": "Research",
"duty": "Collects competitor news"
}
]
}
}Response
{success:true,data:{id,name,owningSpaceId,goal,leadAppId,memberSourcing,collabMode,escalationRouting,status,currentEpochId,createdAt,updatedAt}}Example
curl -sX POST "$HALO_URL/api/teams" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"input":{"name":"Weekly market brief","goal":"Every Monday, summarise competitor moves into brief.md","owningSpaceId":"<spaceId — a uuid from GET /api/spaces>","memberSourcing":"manual","collabMode":"free","escalationRouting":"user","members":[{"appId":"<appId — a digital human from GET /api/apps>","memberName":"Researcher","role":"Research","duty":"Collects competitor news"}]}}'Get one team's own record
GET /api/teams/:teamId Assistant
- For members, edges, live board and waiting decisions use
GET /api/teams/:teamId/detail.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:{id,name,owningSpaceId,goal,leadAppId,memberSourcing,collabMode,escalationRouting,status,currentEpochId,hostNodeId,ephemeral?,createdAt,updatedAt} | null}Example
curl -s "$HALO_URL/api/teams/<teamId>" \
-H "Authorization: Bearer $HALO_TOKEN"Edit a team's name, goal, mode, escalation routing or lead
PATCH /api/teams/:teamId Reversible Assistant
- Only the fields sent change. Also accepts "collabMode": "free"|"structured", "escalationRouting": "user"|"lead", "leadAppId".
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{
"name": "New name",
"goal": "New goal"
}Response
{success:true,data:{id,name,goal,collabMode,escalationRouting,leadAppId,...}}Example
curl -sX PATCH "$HALO_URL/api/teams/<teamId>" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"New name","goal":"New goal"}'Dissolve a team
DELETE /api/teams/:teamId 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. If you only need something narrower, use DELETE /api/teams/:teamId/members/:appId
- Also deletes the Digital Humans Halo created for this team (its lead and the members Halo added) and their workspaces. Digital Humans the user added stay. The run history goes with the team.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true}Example
curl -sX DELETE "$HALO_URL/api/teams/<teamId>" \
-H "Authorization: Bearer $HALO_TOKEN"List files each member produced in the current or latest run
GET /api/teams/:teamId/artifacts Assistant
- Covers the current or latest run. For an earlier run use
GET /api/teams/:teamId/epochs/:epochId/artifacts. Read a file's content withGET /api/artifacts/contentand itspath.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:[{appId,memberName,spaceId,epochId,artifacts:[{name,type:"file"|"folder",path,relativePath,size?,createdAt}]}]}Example
curl -s "$HALO_URL/api/teams/<teamId>/artifacts" \
-H "Authorization: Bearer $HALO_TOKEN"Read one member's transcript within a team
GET /api/teams/:teamId/chat-messages Assistant
- Optional query: &epochId= (a run or conversation id from
GET /api/teams/:teamId/epochs; default: the open run, else the member's own chat, else the latest), &sinceSeq= for incremental reads. - A member on another computer is fetched from its owner; stale:true means a cached copy because the owner is offline, and a 502 means it is unavailable right now.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Query parameters: appId (required), epochId, sinceSeq
Response
{success:true,data:[messages],stale?:true}Example
curl -s "$HALO_URL/api/teams/<teamId>/chat-messages?appId=<appId — a member appId from GET /api/teams/:teamId/detail>" \
-H "Authorization: Bearer $HALO_TOKEN"Stop one periodic check a member set inside a team
DELETE /api/teams/:teamId/checks/:checkId 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.
- checkId comes from checks[] in
GET /api/teams/:teamId/detail.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
checkId | Id of a periodic check a team member set. |
Response
{success:true}Example
curl -sX DELETE "$HALO_URL/api/teams/<teamId>/checks/<checkId>" \
-H "Authorization: Bearer $HALO_TOKEN"List a team's open conversations, newest first
GET /api/teams/:teamId/conversations Assistant
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:[{epochId,teamId,kind:"native"|"im"|"member"|"run"|"collab",label,readonly,memberAppId?,channel?,startedAt,lastActivityAt,active?,waitingUser?}]}Example
curl -s "$HALO_URL/api/teams/<teamId>/conversations" \
-H "Authorization: Bearer $HALO_TOKEN"Open a new team conversation
POST /api/teams/:teamId/conversations Reversible Assistant
- Optional body: "memberAppId" to open it as a one-to-one thread with that member.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{
"title": "optional title"
}Response
{success:true,data:{epochId}}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/conversations" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"optional title"}'Rename a team conversation
PATCH /api/teams/:teamId/conversations/:epochId Reversible Assistant
- Send "title": null to go back to the derived label.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
epochId | Id of one team run or conversation, from GET /api/teams/:teamId/epochs. |
Request body
{
"title": "New title"
}Response
{success:true}Example
curl -sX PATCH "$HALO_URL/api/teams/<teamId>/conversations/<epochId>" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"New title"}'Archive (close) a team conversation
DELETE /api/teams/:teamId/conversations/:epochId 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 conversation stays in run history (
GET /api/teams/:teamId/epochs) but can no longer take messages.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
epochId | Id of one team run or conversation, from GET /api/teams/:teamId/epochs. |
Response
{success:true}Example
curl -sX DELETE "$HALO_URL/api/teams/<teamId>/conversations/<epochId>" \
-H "Authorization: Bearer $HALO_TOKEN"Get a team with members, edges, roster status, board and decisions
GET /api/teams/:teamId/detail Assistant
- The richest single read of a team; start here when the user asks how a team is doing. A pending escalation is answered by the user in Halo, not over this API.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:{team,members:[{appId,memberName,role,duty,isLead,origin,ownerDisplayName,...}],edges:[{fromAppId,toAppId,sync}],roster:[{appId,memberName,role,status:"working"|"idle"|"error"|"waiting_user",currentTaskTitle?}],tasks:[{id,title,assigneeAppId,status,resultRef,note}],findings:[...],activities?,pendingEscalations?:[{appId,memberName,entryId,question}],checks?:[{id,targetMemberName,instruction,schedule,runCount}],boardEpochId,crossesMachines?} | null}Example
curl -s "$HALO_URL/api/teams/<teamId>/detail" \
-H "Authorization: Bearer $HALO_TOKEN"Replace who hands work to whom in a structured team
PUT /api/teams/:teamId/edges Reversible Assistant
- Replaces the whole list. Read the current edges from
GET /api/teams/:teamId/detail, change them and send the full list back. Only meaningful when the team'scollabModeisstructured; member ids come from the same response.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{
"edges": [
{
"fromAppId": "<appId>",
"toAppId": "<appId>",
"sync": false
}
]
}Response
{success:true}Example
curl -sX PUT "$HALO_URL/api/teams/<teamId>/edges" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"edges":[{"fromAppId":"<appId>","toAppId":"<appId>","sync":false}]}'List a team's runs and conversations (history), newest first
GET /api/teams/:teamId/epochs Assistant
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:[{id,startedAt,lastActivityAt,endedAt,endReason,summary,taskCount,doneCount,lifecycle:"run"|"conversation",label?,outcome?:"output"|"no_action"|"escalation"|"failed",triggerType?,artifactCount?}]}Example
curl -s "$HALO_URL/api/teams/<teamId>/epochs" \
-H "Authorization: Bearer $HALO_TOKEN"List files produced during one run
GET /api/teams/:teamId/epochs/:epochId/artifacts Assistant
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
epochId | Id of one team run or conversation, from GET /api/teams/:teamId/epochs. |
Response
{success:true,data:[{appId,memberName,spaceId,epochId,artifacts:[...]}]}Example
curl -s "$HALO_URL/api/teams/<teamId>/epochs/<epochId>/artifacts" \
-H "Authorization: Bearer $HALO_TOKEN"Get the tasks, findings and members of one run
GET /api/teams/:teamId/epochs/:epochId/board Assistant
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
epochId | Id of one team run or conversation, from GET /api/teams/:teamId/epochs. |
Response
{success:true,data:{epoch,tasks:[...],findings:[...],members:[...],activities?} | null}Example
curl -s "$HALO_URL/api/teams/<teamId>/epochs/<epochId>/board" \
-H "Authorization: Bearer $HALO_TOKEN"See which computers of a shared team are online
GET /api/teams/:teamId/federation/presence Assistant
- null means the team is not shared with other computers.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:{officeId,role:"host"|"joined",nodes:[{nodeId,displayName,status,lastSeen}],authority} | null}Example
curl -s "$HALO_URL/api/teams/<teamId>/federation/presence" \
-H "Authorization: Bearer $HALO_TOKEN"Leave a team hosted on someone else's computer
POST /api/teams/:teamId/leave 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 for a team joined from another computer (
hostNodeIdset inGET /api/teams). The members this computer brought leave with it; joining again needs a new invite from the host.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{}Response
{success:true} | {success:false,error}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/leave" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Add an existing digital human to a team
POST /api/teams/:teamId/members Reversible Assistant
- To have Halo create a new digital human for a role, create one first (digital-human group) and add it here. Delegated permissions are set in the Halo app — see this page's withheld list.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{
"appId": "<appId — a digital human from GET /api/apps>",
"memberName": "Reviewer",
"role": "Review",
"duty": "Checks every draft before it is filed"
}Response
{success:true,data:{teamId,appId,memberName,role,duty,isLead,addedAt,...}}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/members" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"appId":"<appId — a digital human from GET /api/apps>","memberName":"Reviewer","role":"Review","duty":"Checks every draft before it is filed"}'Rewrite what a member is responsible for in this team
PATCH /api/teams/:teamId/members/:appId Reversible Assistant
- Only members that run on this computer can be changed; a member someone else brought is refused. Delegated permissions are set in the Halo app — see this page's withheld list.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
appId | Digital Human id, from GET /api/apps. |
Request body
{
"duty": "Owns the pricing section"
}Response
{success:true,data:{teamId,appId,memberName,role,duty,...}}Example
curl -sX PATCH "$HALO_URL/api/teams/<teamId>/members/<appId>" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"duty":"Owns the pricing section"}'Remove a member from a team
DELETE /api/teams/:teamId/members/:appId 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 digital human itself stays installed unless Halo created it only for this team.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
appId | Digital Human id, from GET /api/apps. |
Response
{success:true}Example
curl -sX DELETE "$HALO_URL/api/teams/<teamId>/members/<appId>" \
-H "Authorization: Bearer $HALO_TOKEN"Send a message to one team member and wait for its reply
POST /api/teams/:teamId/members/:appId/send Reversible Assistant
- Blocks until the member finishes its turn (can take minutes) and returns its final message. delivery "queued"/"mid_turn" means the member was busy: the message was accepted but no reply exists yet — read it later with
GET /api/teams/:teamId/chat-messages. - Optional body: "epochId" to address a specific run or conversation. Works for members on other computers too.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
appId | Digital Human id, from GET /api/apps. |
Request body
{
"message": "Please re-check the pricing section"
}Response
{success:true,data:{ok,finalMessage,reason?,delivery?:"queued"|"mid_turn"}}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/members/<appId>/send" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"Please re-check the pricing section"}'Stop the turn one team member is running
POST /api/teams/:teamId/members/:appId/stop Reversible Assistant
- Optional body: "epochId". To stop the whole team run instead,
POST /api/teams/:teamId/pause.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
appId | Digital Human id, from GET /api/apps. |
Request body
{}Response
{success:true,data:{ok,stopped,reason?}} — stopped:false means nothing was runningExample
curl -sX POST "$HALO_URL/api/teams/<teamId>/members/<appId>/stop" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Stop the team's current run
POST /api/teams/:teamId/pause Reversible Assistant
- Seals the run as stopped; members mid-turn are stopped with it. Start again with
POST /api/teams/:teamId/run.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{}Response
{success:true}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/pause" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Start a team run on its goal
POST /api/teams/:teamId/run Reversible Assistant
- Starts a run on the team's goal and returns once it has started; the run continues in the background. The lead works from the team goal, as this call carries no brief.
- Fails for a temporary collaboration (keep it first) and for a team hosted on another computer. Follow progress with
GET /api/teams/:teamId/detailor over the WebSocket.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{}Response
{success:true} — returns once the run has started; it continues in the backgroundExample
curl -sX POST "$HALO_URL/api/teams/<teamId>/run" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Keep a temporary collaboration as a persistent team
POST /api/teams/:teamId/save-collab Reversible Assistant
- Keeps a temporary collaboration as a team, optionally under a new
name. Keep one when the user asked for it.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{
"name": "Market research team"
}Response
{success:true,data:{id,name,owningSpaceId,goal,leadAppId,status,...}}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/save-collab" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Market research team"}'List a team's schedules and other triggers
GET /api/teams/:teamId/triggers Assistant
configis returned as stored, including any secret it holds. Treat the response like a secret.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Response
{success:true,data:[{id,teamId,sourceType:"schedule"|"webhook"|"file"|"wecom",config,enabled,createdAt}]}Example
curl -s "$HALO_URL/api/teams/<teamId>/triggers" \
-H "Authorization: Bearer $HALO_TOKEN"Add a schedule, file or WeCom trigger to a team, or change one
POST /api/teams/:teamId/triggers Reversible Assistant
configbysourceType:scheduletakes{"every": "30m"}or{"cron": "0 9 * * 1"};filetakes{"pattern": "*.csv", "path": "<part of the folder path>"};wecomtakes{"chatId": "<optional>"}.- To change an existing trigger, send
triggerIdnext totrigger; itssourceTypemust stay the same.
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
Request body
{
"trigger": {
"sourceType": "schedule",
"config": {
"cron": "0 9 * * 1"
},
"enabled": true
}
}Response
{success:true,data:{id,teamId,sourceType,config,enabled,createdAt}}Example
curl -sX POST "$HALO_URL/api/teams/<teamId>/triggers" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"trigger":{"sourceType":"schedule","config":{"cron":"0 9 * * 1"},"enabled":true}}'Remove a schedule or other trigger from a team
DELETE /api/teams/:teamId/triggers/:triggerId 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. If you only need something narrower, use POST /api/teams/:teamId/triggers
- To pause one without losing it,
POST /api/teams/:teamId/triggerswith its triggerId and "enabled": false (not a webhook trigger — see this page's withheld list).
Path parameters
| Name | Description |
|---|---|
teamId | Team id, from GET /api/teams. |
triggerId | Trigger id, from GET /api/teams/:teamId/triggers. |
Response
{success:true}Example
curl -sX DELETE "$HALO_URL/api/teams/<teamId>/triggers/<triggerId>" \
-H "Authorization: Bearer $HALO_TOKEN"Get the temporary collaboration started from one conversation
GET /api/teams/collab-for-conversation/:conversationId Assistant
- conversationId is a space conversation id (
GET /api/spaces/:spaceId/conversations). null means that conversation never started a collaboration.
Path parameters
| Name | Description |
|---|---|
conversationId | Conversation id, from GET /api/spaces/:spaceId/conversations. |
Response
{success:true,data:{teamId,name,goal,epochId,active,saved,members:[...]} | null}Example
curl -s "$HALO_URL/api/teams/collab-for-conversation/<conversationId>" \
-H "Authorization: Bearer $HALO_TOKEN"Draft AI member roles for a team goal (creates nothing)
POST /api/teams/propose-members Assistant
- Runs a model call, so it takes a few seconds. Show the draft to the user, then pass it as confirmedProposal to
POST /api/teamswith memberSourcing "ai".
Request body
{
"goal": "Every Monday, summarise competitor moves into brief.md",
"owningSpaceId": "<spaceId — a uuid from GET /api/spaces>"
}Response
{success:true,data:[{memberName,role,responsibility}]} — at most 5Example
curl -sX POST "$HALO_URL/api/teams/propose-members" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"goal":"Every Monday, summarise competitor moves into brief.md","owningSpaceId":"<spaceId — a uuid from GET /api/spaces>"}'