Skip to content

Assistants

Build task flows

Build ordered task flows, link them to assistants, and override task instructions per assistant.

A task group is a named, ordered sequence of tasks: a structured flow an assistant can follow, such as a booking flow that collects party size, then date, then a contact number. Task groups belong to your account rather than to a single assistant: you link a group to as many assistants as need it, and any one of those assistants can override an individual task's instructions without forking the group.

Sixteen endpoints cover the whole surface. They fall into four sets: the groups themselves, the tasks inside a group, the link between an assistant and a group, and the per-assistant instruction overrides. Every field and status code is also listed in the Task Groups API reference.

MethodPathPurpose
GET/taskgroupsList task groups
POST/taskgroupsCreate a task group
GET/taskgroups/{id}Retrieve one task group with its tasks
PATCH/taskgroups/{id}Update a group's name or description
DELETE/taskgroups/{id}Delete a task group
GET/taskgroups/{id}/tasksList the tasks in a group
POST/taskgroups/{id}/tasksAdd a task to a group
PATCH/taskgroups/{id}/tasks/{taskId}Update a task
POST/taskgroups/{id}/tasks/reorderReorder the tasks in a group
DELETE/taskgroups/{id}/tasks/{taskId}Delete a task
GET/assistants/{id}/taskgroupsList the groups linked to an assistant
POST/assistants/{id}/taskgroupsLink a group to an assistant
DELETE/assistants/{id}/taskgroups/{taskgroupId}Unlink a group from an assistant
GET/assistants/{id}/tasks/overridesList an assistant's task overrides
POST/assistants/{id}/tasks/{taskId}/overrideSet an instruction override
DELETE/assistants/{id}/tasks/{taskId}/overrideRemove an instruction override

Overview#

Four objects, in the order you create them:

text
task group                       name + description, owned by your account
  └── task                       order + name + description + instructions
assistant ──link──►  task group  one link row per assistant/group pair
assistant ──override──► task     replacement instructions for one task, one assistant

A typical build is five calls:

  1. POST /taskgroups: create the group.
  2. POST /taskgroups/{id}/tasks: add each task, giving every one an order.
  3. POST /assistants/{id}/taskgroups: link the group to an assistant.
  4. PATCH /assistants/{id}: set taskDrivenMode to true on that assistant.
  5. POST /assistants/{id}/tasks/{taskId}/override: only where one assistant needs a different instruction for a shared task.

Task-driven mode is the switch

Linking a group to an assistant does not by itself change how the assistant talks. Assistants carry a taskDrivenMode boolean, set through POST /assistants or PATCH /assistants/{id}, which defaults to false. When it is true, the assistant's linked task groups and their tasks are supplied to the conversation as structured data; when it is false, the standard prompt pipeline runs unchanged. See Assistants.

Every response on this page uses the standard envelope (success, data, metadata) described in API conventions. The shapes below are what you get in data. Errors use the same envelope with error in place of data; the codes are in Errors.

The task group and task shapes#

Both objects come back from several endpoints, so they are described once here and referred to below.

Task group

FieldTypeDescription
idstringThe group's UUID
tenantIdstringThe account that owns the group
namestringThe group's name
descriptionstringWhat the flow is for
tasksarrayThe group's tasks (present on the single-group, create, update and delete responses)
isDeletedbooleanDeletion flag
createdAtstring | nullISO 8601 timestamp
updatedAtstring | nullISO 8601 timestamp
deletedAtstring | nullISO 8601 timestamp, null while the group is live

Task

FieldTypeDescription
idstringThe task's UUID
tenantIdstringThe account that owns the task
taskgroupIdstringThe group the task belongs to
ordernumberThe task's position in the flow
namestringThe task's name
descriptionstringWhat the task achieves
instructionsstringHow the assistant should carry the task out
outputsstring[] | nullNames of the values the task is expected to produce
toolNamesstring[] | nullNames of the tools the task may use
typestring | nullA label for the kind of task
isDeletedbooleanDeletion flag
createdAtstring | nullISO 8601 timestamp
updatedAtstring | nullISO 8601 timestamp
deletedAtstring | nullISO 8601 timestamp, null while the task is live

Placeholders in task instructions#

A task's instructions can reference an output produced by an earlier task in the same group, using a single-brace placeholder:

Two tasks in one group
{ "order": 1, "name": "collect_party_size", "outputs": ["party_size"] }
{ "order": 2, "name": "confirm_booking",
  "instructions": "Read the booking back: a table for {party_size}." }

A placeholder name must start with a letter or underscore. {1st_choice} is rejected as malformed rather than treated as literal text. That check exists to catch typos that would otherwise reach a caller verbatim.

References run forward only#

A task may only reference outputs from tasks at an earlier order. Referencing its own outputs, or those of any task at the same order or later, is rejected when you save it:

text
Task at order 2 references forward/own outputs: booking_ref.
Placeholders may only reference outputs from tasks at earlier orders.

This is why reordering can fail: moving a task above one whose output it depends on breaks the rule.

A placeholder that matches no earlier output is not an error: it passes validation and may resolve from the call's own context at run time. So a typo in an output name fails silently rather than loudly; check the name against the producing task.

{{double braces}} are not an escape

Doubling the braces does not produce a literal. {{party_size}} contains {party_size}, which is matched and validated exactly as the single-brace form. There is no way to write a literal {name} in a task instruction. Note this differs from an assistant's developerPrompt, which uses {{placeholder}} for its parameters; the two syntaxes are not interchangeable.

Create a task group#

POST /taskgroups

FieldTypeRequiredDescription
namestringYesMust match ^[a-z0-9_]+$ (lowercase letters, digits and underscores only)
descriptionstringYesWhat the flow is for
bash
curl -X POST https://core-api.heysadie.ai/taskgroups \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "table_booking",
    "description": "Collects everything needed to book a table."
  }'

data is the new task group, with a tasks array you have not filled in yet:

Response
{
  "success": true,
  "data": {
    "id": "0195f1e2-5555-7000-8000-000000000000",
    "tenantId": "0195f1e2-1111-7000-8000-000000000000",
    "name": "table_booking",
    "description": "Collects everything needed to book a table.",
    "isDeleted": false,
    "createdAt": "2026-07-30T12:00:00.000Z",
    "updatedAt": "2026-07-30T12:00:00.000Z",
    "deletedAt": null,
    "tasks": []
  },
  "metadata": {
    "timestamp": "2026-07-30T12:00:00.000Z",
    "version": "1.0",
    "path": "/taskgroups"
  }
}

Keep the id: every task, link and override call below needs it.

List task groups#

GET /taskgroups

Query paramTypeRequiredDescription
limitnumberNoPage size. Default 50, maximum 500.
offsetnumberNoItems to skip. Default 0.
bash
curl "https://core-api.heysadie.ai/taskgroups?limit=50&offset=0" \
  -H "Authorization: ApiKey YOUR_API_KEY"

data has two keys: taskgroups, an array of task group objects, and total, the number of groups available. Page through it by advancing offset until you have seen total groups.

Response
{
  "success": true,
  "data": {
    "taskgroups": [
      {
        "id": "0195f1e2-5555-7000-8000-000000000000",
        "tenantId": "0195f1e2-1111-7000-8000-000000000000",
        "name": "table_booking",
        "description": "Collects everything needed to book a table.",
        "isDeleted": false,
        "createdAt": "2026-07-30T12:00:00.000Z",
        "updatedAt": "2026-07-30T12:00:00.000Z",
        "deletedAt": null
      }
    ],
    "total": 1
  },
  "metadata": {
    "timestamp": "2026-07-30T12:05:00.000Z",
    "version": "1.0",
    "path": "/taskgroups"
  }
}

Retrieve a task group#

GET /taskgroups/{id} returns one group with its tasks nested under tasks: the whole flow in a single call, which is the endpoint to use when you want to render or diff a complete procedure.

bash
curl https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000 \
  -H "Authorization: ApiKey YOUR_API_KEY"

data is a single task group object, including tasks. A group that does not exist (or belongs to another account) returns 404 with RESOURCE_NOT_FOUND.

Update a task group#

PATCH /taskgroups/{id} changes the group's own metadata. Both fields are optional; send only what you are changing. To edit what the assistant actually does, edit the tasks, not the group.

FieldTypeRequiredDescription
namestringNoMust match ^[a-z0-9_]+$
descriptionstringNoWhat the flow is for
bash
curl -X PATCH https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000 \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Books a table and captures dietary requirements." }'

data is the updated task group object with its tasks array, so you can use the response to refresh your local copy without a follow-up GET.

Delete a task group#

DELETE /taskgroups/{id} returns the task group object it removed: id, name, description, the tasks array, and the isDeleted / deletedAt fields that record the deletion.

bash
curl -X DELETE https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000 \
  -H "Authorization: ApiKey YOUR_API_KEY"

Deleting is a soft delete, and it cascades

DELETE /taskgroups/{id} does three things in one operation:

  1. marks the group deleted,
  2. marks every task in it deleted,
  3. removes the group's links to every assistant that had it attached.

Deleted groups and tasks stop appearing in list results (there is no includeDeleted-style filter), so treat the operation as irreversible from the API's point of view even though the records are retained rather than erased.

You do not need to unlink the group from assistants first; the delete does it for you. But any assistant that was using it loses that flow immediately, so check GET /assistants/{id}/taskgroups across your assistants before deleting a shared group.

Add a task#

POST /taskgroups/{id}/tasks adds one task to the group. Tasks are what the assistant actually follows: description says what the step is for, instructions says how to carry it out.

FieldTypeRequiredDescription
ordernumberYesPosition in the flow
namestringYesThe task's name
descriptionstringYesWhat the task achieves
instructionsstringYesHow the assistant should carry the task out
outputsstring[] | nullNoNames of the values the task should produce
toolNamesstring[] | nullNoNames of the tools this task may use
typestring | nullNoA label for the kind of task
bash
curl -X POST https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000/tasks \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order": 0,
    "name": "Collect party size",
    "description": "Find out how many people the booking is for.",
    "instructions": "Ask the caller how many guests will be dining. Accept 1 to 20; if they say more, tell them large parties are booked by the events team.",
    "outputs": ["party_size"]
  }'

data is the created task:

Response
{
  "success": true,
  "data": {
    "id": "0195f1e2-6666-7000-8000-000000000000",
    "tenantId": "0195f1e2-1111-7000-8000-000000000000",
    "taskgroupId": "0195f1e2-5555-7000-8000-000000000000",
    "order": 0,
    "name": "Collect party size",
    "description": "Find out how many people the booking is for.",
    "instructions": "Ask the caller how many guests will be dining. Accept 1 to 20; if they say more, tell them large parties are booked by the events team.",
    "outputs": ["party_size"],
    "toolNames": null,
    "type": null,
    "isDeleted": false,
    "createdAt": "2026-07-30T12:10:00.000Z",
    "updatedAt": "2026-07-30T12:10:00.000Z",
    "deletedAt": null
  },
  "metadata": {
    "timestamp": "2026-07-30T12:10:00.000Z",
    "version": "1.0",
    "path": "/taskgroups/0195f1e2-5555-7000-8000-000000000000/tasks"
  }
}

Repeat the call for each step, incrementing order. Keep the returned id values: a task's id is what an override is set against.

List tasks#

GET /taskgroups/{id}/tasks returns the group's tasks. data is an array of task objects: the same shape as the create response, not wrapped in an object and with no total.

bash
curl https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000/tasks \
  -H "Authorization: ApiKey YOUR_API_KEY"

order is the field that defines the sequence. The specification does not state the array's sort order, so sort by order client-side (as the snippet above does) rather than relying on array position.

Update a task#

PATCH /taskgroups/{id}/tasks/{taskId} takes the same fields as create, all optional. This is where you change a shared step for every assistant using the group; to change it for one assistant only, set an override instead.

FieldTypeRequiredDescription
ordernumberNoPosition in the flow
namestringNoThe task's name
descriptionstringNoWhat the task achieves
instructionsstringNoHow the assistant should carry the task out
outputsstring[] | nullNoNames of the values the task should produce
toolNamesstring[] | nullNoNames of the tools this task may use
typestring | nullNoA label for the kind of task
bash
curl -X PATCH https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000/tasks/0195f1e2-6666-7000-8000-000000000000 \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructions": "Ask how many guests will be dining, including children."
  }'

data is the updated task object, including the fields you did not send.

Reorder tasks#

POST /taskgroups/{id}/tasks/reorder sets the position of tasks in one call, which is what you want after a drag-and-drop edit: send the whole flow rather than issuing a PATCH per task and leaving the group briefly inconsistent.

FieldTypeRequiredDescription
orderarrayYesObjects pairing a task with its new position
order[].taskIdstring (UUID)YesThe task to move
order[].ordernumberYesIts new position
bash
curl -X POST https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000/tasks/reorder \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order": [
      { "taskId": "0195f1e2-7777-7000-8000-000000000000", "order": 0 },
      { "taskId": "0195f1e2-6666-7000-8000-000000000000", "order": 1 }
    ]
  }'

data is an array of task objects carrying their new order values: the same shape GET /taskgroups/{id}/tasks returns, so you can render the result straight from the response.

A reorder must list every task, exactly once

The order array is a complete new layout, not a patch. Four rules are enforced:

  • every taskId must be a live task in this group: unknown IDs are rejected and named;
  • the array must contain every task in the group, exactly once: omitting one or repeating one is rejected with Reorder payload must include every task in the taskgroup exactly once;
  • no two tasks may share an order value;
  • the resulting layout is re-checked against the forward-only placeholder rule.

That last one is the surprising failure: moving a task above one whose output it references is rejected, because the reference would then point forward. Read the group, reorder the whole list locally, and send it back complete.

Delete a task#

DELETE /taskgroups/{id}/tasks/{taskId} removes one task from the group. data is the task object that was removed, carrying isDeleted and deletedAt.

bash
curl -X DELETE https://core-api.heysadie.ai/taskgroups/0195f1e2-5555-7000-8000-000000000000/tasks/0195f1e2-6666-7000-8000-000000000000 \
  -H "Authorization: ApiKey YOUR_API_KEY"

Deleting a task leaves gaps in the remaining order values. Follow up with a reorder call if your flow depends on contiguous positions.

POST /assistants/{id}/taskgroups links an existing group to an assistant. The group is unchanged by this; linking is a relationship, so the same group can be linked to as many assistants as you like.

FieldTypeRequiredDescription
taskgroupIdstring (UUID)YesThe group to link
bash
curl -X POST https://core-api.heysadie.ai/assistants/0195f1e2-2222-7000-8000-000000000000/taskgroups \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "taskgroupId": "0195f1e2-5555-7000-8000-000000000000" }'

The response is the standard envelope with success: true and metadata; the specification does not define a payload for data on this endpoint, so branch on success and read the resulting state from GET /assistants/{id}/taskgroups rather than from the create response.

Remember that a link alone changes nothing until taskDrivenMode is true on the assistant.

List an assistant's task groups#

GET /assistants/{id}/taskgroups returns everything linked to one assistant, each group with its tasks nested. That is enough to render the assistant's full set of flows in a single call.

FieldTypeDescription
linkIdstringThe link between this assistant and this group
assistantIdstringThe assistant
taskgroupIdstringThe linked group
namestringThe group's name
descriptionstringThe group's description
tasksarrayThe group's tasks, in the task shape described above
bash
curl https://core-api.heysadie.ai/assistants/0195f1e2-2222-7000-8000-000000000000/taskgroups \
  -H "Authorization: ApiKey YOUR_API_KEY"
Response
{
  "success": true,
  "data": [
    {
      "linkId": "0195f1e2-8888-7000-8000-000000000000",
      "assistantId": "0195f1e2-2222-7000-8000-000000000000",
      "taskgroupId": "0195f1e2-5555-7000-8000-000000000000",
      "name": "table_booking",
      "description": "Collects everything needed to book a table.",
      "tasks": [
        {
          "id": "0195f1e2-6666-7000-8000-000000000000",
          "tenantId": "0195f1e2-1111-7000-8000-000000000000",
          "taskgroupId": "0195f1e2-5555-7000-8000-000000000000",
          "order": 0,
          "name": "Collect party size",
          "description": "Find out how many people the booking is for.",
          "instructions": "Ask the caller how many guests will be dining.",
          "outputs": ["party_size"],
          "toolNames": null,
          "type": null,
          "isDeleted": false,
          "createdAt": "2026-07-30T12:10:00.000Z",
          "updatedAt": "2026-07-30T12:10:00.000Z",
          "deletedAt": null
        }
      ]
    }
  ],
  "metadata": {
    "timestamp": "2026-07-30T12:20:00.000Z",
    "version": "1.0",
    "path": "/assistants/0195f1e2-2222-7000-8000-000000000000/taskgroups"
  }
}

The nested tasks carry the group's shared instructions. Overrides are not merged into this response. Read them from GET /assistants/{id}/tasks/overrides and apply them against taskId if you are showing an operator what one assistant will actually say.

DELETE /assistants/{id}/taskgroups/{taskgroupId} removes the link between that assistant and that group. It addresses the link, not the group: the group itself is a separate resource with its own DELETE /taskgroups/{id}, and every other assistant linked to it is untouched.

bash
curl -X DELETE https://core-api.heysadie.ai/assistants/0195f1e2-2222-7000-8000-000000000000/taskgroups/0195f1e2-5555-7000-8000-000000000000 \
  -H "Authorization: ApiKey YOUR_API_KEY"

The response is the standard envelope with success: true; the specification defines no payload for data.

Override a task for one assistant#

This is the mechanism that keeps one flow usable across many assistants, so it is worth being precise about what it does.

A task group is shared. Its tasks (including their instructions) belong to the group, and editing a task with PATCH /taskgroups/{id}/tasks/{taskId} changes it for every assistant linked to that group. That is usually what you want. What you do not want is to duplicate an eight-step booking flow because one restaurant needs to ask about outdoor seating.

An override solves exactly that. POST /assistants/{id}/tasks/{taskId}/override records replacement instruction text for one task, against one assistant. It writes nothing to the task group, so:

  • the shared task keeps its original instructions;
  • every other assistant linked to the group is unaffected;
  • there is no second copy of the flow to keep in sync: structural edits still happen once, on the group.

Scope it correctly: an override replaces instruction text for a single task. It cannot add a task, remove one, or change the order for one assistant. Those are properties of the group, and an assistant that needs a genuinely different sequence needs a different group.

FieldTypeRequiredDescription
instructionOverridestringYesThe instruction text to use for this task on this assistant
bash
curl -X POST https://core-api.heysadie.ai/assistants/0195f1e2-2222-7000-8000-000000000000/tasks/0195f1e2-6666-7000-8000-000000000000/override \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructionOverride": "Ask how many guests will be dining, then ask whether they would prefer indoor or outdoor seating."
  }'

The response is the standard envelope with success: true; data contains the stored override record. You can also read every active override with GET /assistants/{id}/tasks/overrides.

Re-posting an override updates it

Four behaviours are part of the current contract:

  • POST is idempotent in effect. If an override already exists for that assistant-and-task pair, a second POST updates the stored text rather than failing with a conflict, so you can treat it as an upsert.
  • The group must be actively linked. You can only override a task whose task group is currently attached to that assistant. Unlink the group and the override has nothing to apply to.
  • The override replaces the base instructions completely. It is not appended or merged. Structural fields such as task name, order, tools, and outputs still come from the shared task.
  • null is not a write operation. The request requires a non-empty string. Use DELETE /assistants/{id}/tasks/{taskId}/override to restore the shared task instructions. A nullable value in a list response is not an instruction to suppress the task; only a non-null active override is applied.

List an assistant's overrides#

GET /assistants/{id}/tasks/overrides returns every override recorded against one assistant. data is an array:

FieldTypeDescription
overrideIdstringThe override record
assistantIdstringThe assistant the override applies to
taskIdstringThe task whose instructions are overridden
taskgroupIdstringThe group that task belongs to
instructionOverridestring | nullThe replacement instruction text
bash
curl https://core-api.heysadie.ai/assistants/0195f1e2-2222-7000-8000-000000000000/tasks/overrides \
  -H "Authorization: ApiKey YOUR_API_KEY"
Response
{
  "success": true,
  "data": [
    {
      "overrideId": "0195f1e2-9999-7000-8000-000000000000",
      "assistantId": "0195f1e2-2222-7000-8000-000000000000",
      "taskId": "0195f1e2-6666-7000-8000-000000000000",
      "taskgroupId": "0195f1e2-5555-7000-8000-000000000000",
      "instructionOverride": "Ask how many guests will be dining, then ask whether they would prefer indoor or outdoor seating."
    }
  ],
  "metadata": {
    "timestamp": "2026-07-30T12:30:00.000Z",
    "version": "1.0",
    "path": "/assistants/0195f1e2-2222-7000-8000-000000000000/tasks/overrides"
  }
}

taskId is the join key. Pair this response with GET /assistants/{id}/taskgroups (as the Node.js snippet's byTaskId map does) to show, task by task, which steps this assistant runs as shared and which it runs its own way.

Remove an override#

DELETE /assistants/{id}/tasks/{taskId}/override deletes the override record for that assistant and task, leaving the assistant with the group's shared task as it stands.

bash
curl -X DELETE https://core-api.heysadie.ai/assistants/0195f1e2-2222-7000-8000-000000000000/tasks/0195f1e2-6666-7000-8000-000000000000/override \
  -H "Authorization: ApiKey YOUR_API_KEY"

The response is the standard envelope with success: true; the specification defines no payload for data. Confirm the removal with GET /assistants/{id}/tasks/overrides: the entry for that taskId is gone.

Next steps#