> ## Documentation Index
> Fetch the complete documentation index at: https://heybtw.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# save_task

> Record a follow-up the user accepted as a task on a collab, with an owner and an event-anchored due date, then mark it done.

> [!WARNING]
>
> Writes to your heyBTW workspace. It creates or completes one follow-up task, heyBTW's record of the commitment. It writes to no customer system and sends nothing; a shared task becomes visible to the partner workspaces assigned to it.

The marketer's question is "who owes what after this event, and by when?" Reach for this tool when the user accepts a follow-up in the conversation, and again when they say it is done.

Follows the [heyBTW MCP rules](https://heybtw.com/docs/mcp/rules.md).

## When to use it

* "Yes, have Bob send Jane the pricing deck three days after the dinner."
* "Tony books the meeting with Rosa's CRO by the 30th."
* "Have Brightpath and Ferrocity each send invites to their top 20 by Friday."
* "Done, Theo has the proposal."

## Parameters

| Parameter | Type | Required | Notes |
| - | - | - | - |
| `action` | string | Yes | `create` or `complete`. |
| `client_request_id` | string | Yes | A fresh hyphenated UUID for each call. The same id replays the call. |
| `channel_id` | string | create | The collab, from list\_events. |
| `title` | string | create | What will be done, one line, at most 300 characters. |
| `owner_email` | string | create, unless `partner_domains` | Who owns it: a team member or someone outside the workspace, such as an AE. |
| `contact_email` | string | One of `contact_email` or `account_domain` (create, own task) | The email this workspace holds for the person. |
| `account_domain` | string | See above | The company's domain. Stored as given; a gap says it was not checked. |
| `due_offset_days` | integer | No | Days from the collab's event date: negative before, 0 the event day. At most 3650. |
| `due_at` | string | No | A calendar date `YYYY-MM-DD` in the collab's time zone. Send one of `due_offset_days` or `due_at`, never both. A collab with no event date cannot take `due_at`. |
| `proposed_by` | string | create | `agent` when you proposed it, `user` when the user asked for it. |
| `reason` | string | No | One line on why, at most 500 characters. |
| `observation_ref` | string | No | The `observation_id` [record\_observation](https://heybtw.com/docs/tools/record-observation.md) returned on this collab. The task points at it and copies nothing. |
| `partner_domains` | list | No | 1 to 10 partner domains on a collab this workspace hosts. Makes a shared task. |
| `completion_rule` | string | No | With `partner_domains`: `each` (default) or `any`. |
| `task_id` | string | complete | The id create returned. |
| `outcome_note` | string | No | complete only: how it went, at most 2,000 characters. |
| `contract_version` | string | No | Omit for v1. Only v1 is advertised. |

Either way the due date is stored relative to the event and moves if the event moves.

## Workflow

**1. Wait for the user's yes**

One accepted follow-up is one call. Never create tasks in bulk, or from suggestions the user did not accept.

**2. Create**

Send `action: "create"` with a fresh `client_request_id`. Hand the task to its owner through the user's own connectors (a CRM task, a Slack or mail message), joined on `owner_email` and `contact_email`.

**3. Complete**

When the user says it is done, send `action: "complete"` with `task_id`, an optional `outcome_note`, and a new `client_request_id`. A task already done changes nothing and returns `outcome: "already_completed"`.

**4. Retry safely**

On a transport error, retry with the SAME `client_request_id`: a committed call returns `idempotent_replay: true` and is never repeated. Reusing the id for a different call is refused.

### Shared tasks for partners

Send `partner_domains` to give the task to partner companies on a collab you host (manage\_collab\_partners `action: "status"` lists them). Each partner whose heyBTW workspace has joined the collab gets its own assignment and sees the task in its own heyBTW.

* `completion_rule: "each"`: every partner does it. `"any"`: done when one partner does it; the others are `covered` and owe nothing.
* A shared task names no person. Never send `owner_email`, `contact_email` or `observation_ref` with `partner_domains`, and never put a person's email in the title or reason. Title, reason and `account_domain` are visible to every assigned partner.
* A domain that is not a partner, was removed, or is your own is rejected by name; the rest still lands.
* `partner_not_joined`: add it again with manage\_collab\_partners `action: "add"`. If still refused, invite its contact with `action: "invite"` (with the user's go-ahead) and save the task once they accept.
* `partner_no_workspace`: record a task of your own workspace instead (`owner_email` the partner's contact, `account_domain` its domain) and send the ask through the user's connectors.
* On complete, a shared task completes only this workspace's own assignment. The host cannot complete it for a partner.

## Response

The success shape below is from the code (`tools/save_task.py` and `save-task-contract.json`), not a live call: this page was written against a production workspace, so no task was created. `data[0]` holds:

| Field | Meaning |
| - | - |
| `outcome` | create: `created` or `nothing_saved`. complete: `completed` or `already_completed`. |
| `task` | The task, or null on `nothing_saved`. |
| `task.task_id`, `title`, `channel_id`, `owner_email`, `person` | The task and who it is about (person by email). |
| `task.refs[]` | `{type: "account", ref}` for the account. |
| `task.due_date`, `due_timezone`, `due_gap` | A calendar date in the collab's zone, or a `due_gap` (`no_event_date`, `no_due_offset`) saying why there is none. |
| `task.event_window` | Where the due date falls on heyBTW's event clock (bands below). Null whenever `due_gap` is set. |
| `task.provenance` | `proposed_by`, `reason`, `accepted_by`, `observation_ref`. |
| `task.completed_at`, `completed_by` | Set once done. |
| `task.scope`, `completion_rule`, `task_state`, `assignments[]` | Shared tasks only. `assignments` (on create) is one `{ref, status}` per partner. |
| `rejected_count`, `rejected[]` | create only: `{ref, field, reason}` per refused reference. |
| `idempotent_replay` | `true` when this call replayed an earlier one. |

`event_window` bands, served by heyBTW, never computed by you:

| Band | When the due date falls |
| - | - |
| `pre_event` | More than 14 days before the event day |
| `sprint` | 14 to 8 days before |
| `event_week` | The last 7 days and the event day |
| `wind_down` | The 14 days after |
| `wrapped` | Later |

The validation error below is a real response, trimmed, from `action: "create"` sent with nothing else:

```json theme={null}
{
  "tool": "save_task",
  "status": "error",
  "data": null,
  "error": {
    "code": "invalid_parameter",
    "message": "A tool parameter is invalid",
    "what_failed": "A tool parameter (`client_request_id`) must be a hyphenated UUID that you generate fresh for each distinct call.",
    "what_to_check": "The request arguments against this tool's input schema.",
    "what_to_do": "Generate a new UUID for this call. Reuse a client_request_id only to retry the identical call. Nothing was written by this one.",
    "retryable": false,
    "remediation": null
  }
}
```

## Rules the agent must follow

* Create a task only for a follow-up the user accepted. One per call.
* A task is a commitment, not evidence of contact. Never report a task as a touch; [get\_attendee\_touch\_status](https://heybtw.com/docs/tools/get-attendee-touch-status.md) reports contact.
* Read `event_window` and `due_date` as served. Never compute a band or a date yourself.
* Never send `owner_email`, `contact_email` or `observation_ref` with `partner_domains`.
* heyBTW sends nothing here. Deliver the task to its owner through the user's own connectors.

## Errors and gaps

| Code | When | What to do |
| - | - | - |
| `invalid_parameter` | `action` missing or not `create`/`complete`; a missing or malformed `client_request_id`; any other malformed argument. Reported one parameter at a time. | Fix the named parameter. |
| `entity_not_found` gap | A contact the workspace has no record of, a personal or non-business domain, an observation not on this collab, or refused partner domains (in their own gap). | The rest of the call still landed. Name each refusal to the user. |
| `outcome: "nothing_saved"` | Every contact and account, or every partner domain, was rejected, or the collab is not one this workspace is in. | No task exists. Fix the references and call again with a new id. |
| `insufficient_scope` | An OAuth token without `mcp:tools:write`. | Nothing was saved. |
| `rate_limit_exceeded` | Per-workspace limits. | Honor Retry-After. |
| `internal_error` | Transient. | Retry once with the same `client_request_id`. |

## Related

* [get\_due\_tasks](https://heybtw.com/docs/tools/get-due-tasks.md): what is still owed, overdue, or owed by a partner.
* [record\_observation](https://heybtw.com/docs/tools/record-observation.md): the debrief facts a task can point at.
* Playbooks: [The morning after](https://heybtw.com/docs/playbooks/morning-after.md), [Follow-up and ROI](https://heybtw.com/docs/playbooks/follow-up-and-roi.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.