# Notes & tasks

Three kinds of records live here:

- **Notes and logged interactions.** A team note on a job, or a call / email / meeting / site visit that already happened. Calls, emails and meetings logged on a job also land on the customer's and the person's feed, exactly as when they are logged in the app. Logging records the conversation; **nothing here sends an email or a text**.
- **Activity and history.** A job's activity log (stage moves, notes, task lines, files) and a customer's or person's merged history (emails and texts, calls, notes, follow-ups, stage moves, proposals, signatures, invoices).
- **Follow-ups (tasks).** Job follow-ups (`/v1/tasks`) and company follow-ups that need no job (`/v1/account-tasks`). They show in each person's My day queue and send the assignee a reminder.

API writes act as the user who created the API key: notes and completed follow-ups show that person as the author.

!!! note
    Partner keys that act for several workspaces must also send `firm_id` (query string, or JSON body on writes).

Lists return `{"data": [...], "pagination": {...}}` (default 50, max 200). Timestamps are UTC ISO 8601 with `Z`.

## Job notes and activity

### The activity object

| Field | Type | Description |
|---|---|---|
| `id` | integer | TruTec id. |
| `job_id` | integer | The job. |
| `type` | string | `note`, `call`, `email`, `meeting`, `site_visit`, `status_change`, `task`, `created`, `details_updated`, `assigned`, ... |
| `content` | string or null | The text. |
| `is_team_note` | boolean | An authored team note (not a system line). |
| `user` | object or null | `{id, name}` of the author. |
| `details` | object | Extras when present: `old_status`, `new_status`, `outcome` (call outcome, or a lost reason's answers), `phone`, `task_id`, `customer_response`, ... |
| `created_at` | string | UTC. |

### List job activity

`GET /v1/jobs/{job_id}/activity`

**Permission:** Read jobs

Everything on the job's activity log, newest first.

| Name | Type | Required | Description |
|---|---|---|---|
| `type` | string | No | Comma-separated types to keep, e.g. `status_change,call`. |
| `created_after` | string | No | ISO 8601. |
| `limit`, `offset` | integer | No | Paging. |

```json
{
  "data": [
    {
      "id": 990211,
      "job_id": 41872,
      "type": "call",
      "content": "Left voicemail · 512-555-0142\nWill try Thursday",
      "is_team_note": false,
      "user": {"id": 118, "name": "Dana Whitfield"},
      "details": {"source": "click_to_call", "outcome": "left_voicemail", "phone": "512-555-0142"},
      "created_at": "2026-10-12T15:10:44Z"
    }
  ],
  "pagination": {"total": 1, "limit": 50, "offset": 0}
}
```

### List job notes

`GET /v1/jobs/{job_id}/notes`

**Permission:** Read jobs

What people wrote: team notes plus logged calls, emails, meetings and site visits (no system lines). Same parameters as the activity list.

### Add a job note or log a call

`POST /v1/jobs/{job_id}/notes`

**Permission:** Write jobs

| Name | Type | Required | Description |
|---|---|---|---|
| `content` | string | Yes | Up to 8,000 characters. |
| `type` | string | No | `note` (default), `call`, `email`, `meeting` or `site_visit`. |
| `outcome` | string | No | Calls: e.g. `connected`, `left_voicemail`, `no_answer`. Shown before the text. |
| `phone` | string | No | Calls: the number dialed. |
| `customer_response` | boolean | No | The customer replied (calls, emails, meetings). Stops the job's automatic sales sequence, as in the app. |
| `task_id` | integer | No | A follow-up on this job: the entry becomes that follow-up's notes. |
| `complete_task` | boolean | No | With `task_id`: also complete it. Default `true`. |

```bash
curl -X POST "https://trutec.ai/api/v1/jobs/41872/notes" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"type": "call", "outcome": "left_voicemail", "phone": "512-555-0142", "content": "Will try Thursday"}'
```

Returns `201` with `{"data": {activity}}`, plus `task_id` / `task_completed` when filed against a follow-up and `stopped_sequence_tasks` with `customer_response`. Connected integrations hear about the note as they do for notes added in the app.

Tagging teammates in a note is not available through the API.

**Errors:** `400` `content is required`, `type must be one of: ...`; `404` `Job not found`, `task_id is not a follow-up on this job`.

## Contact and customer notes

A person's feed and a customer's feed (company notes plus its people's notes, calls, emails, meetings and texts).

### The feed note object

| Field | Type | Description |
|---|---|---|
| `id` | integer | TruTec id. |
| `type` | string | `note`, `call`, `email`, `meeting` or `text`. |
| `content` | string or null | The text. |
| `contact_id`, `client_account_id`, `job_id` | integer or null | What it is about. |
| `user` | object or null | `{id, name}` of the author. |
| `details` | object | Extras when present (call outcome, email subject, direction, ...). |
| `created_at` | string | UTC. |

### List a contact's notes / a customer's notes

`GET /v1/contacts/{id}/notes`

`GET /v1/client-accounts/{id}/notes`

**Permission:** Read jobs

Newest first. Filters: `type` (comma-separated), `created_after`, `limit`, `offset`.

### Add a note to a contact / a customer

`POST /v1/contacts/{id}/notes`

`POST /v1/client-accounts/{id}/notes`

**Permission:** Write jobs

| Name | Type | Required | Description |
|---|---|---|---|
| `content` | string | Yes | Up to 4,000 characters. |
| `type` | string | No | `note` (default), `call`, `email` or `meeting`. |
| `outcome`, `phone` | string | No | Calls only, as for job notes. |
| `contact_id` | integer | No | Customer notes only: one of the customer's people the note is about. |

A note on a person also shows on their company's feed.

```bash
curl -X POST "https://trutec.ai/api/v1/contacts/20417/notes" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"type": "meeting", "content": "Walked the lot with the board"}'
```

```json
{
  "data": {
    "id": 55120,
    "type": "meeting",
    "content": "Walked the lot with the board",
    "contact_id": 20417,
    "client_account_id": 4812,
    "job_id": null,
    "user": {"id": 118, "name": "Dana Whitfield"},
    "details": {"source": "api"},
    "created_at": "2026-10-12T15:12:01Z"
  }
}
```

**Errors:** `400` `content is required`, `That contact belongs to another company`; `404` for a record outside the workspace.

## Activity sync (two-way CRM timelines)

Keep a CRM's timeline and TruTec's in step without duplicates:

- **Into TruTec:** `POST /v1/activities` logs a call, text, email, meeting, site visit or note that happened in your CRM, on the job and the customer's feed, keyed on your `source` + `external_id`.
- **Out of TruTec:** the `activity_logged` [webhook](webhooks-and-events.md) fires once for every call, text, email, meeting or site visit logged in TruTec. `GET /v1/activities` returns the same objects for a backfill.
- **No echoes:** anything you post with a `source` other than `trutec` is never sent back out as `activity_logged`.
- **Calls made in TruTec:** each estimator can have their own calling number and call from TruTec (in the browser, or TruTec rings their phone first). The call is logged as it starts and `activity_updated` follows as it ends, as the outcome is saved and as an AI summary arrives. See the `call` object below.
- **Built-in HubSpot sync:** a workspace connected on Settings → HubSpot can switch on "Log calls, texts, emails and meetings in HubSpot" (TruTec creates real HubSpot calls, emails, meetings and SMS communications on the contact, company and deal, and updates the same call when its outcome arrives) and "Show HubSpot activity in TruTec" (HubSpot calls, emails, meetings, notes and communications appear on the linked job, customer and person, `source: hubspot`). An engagement TruTec created is known by its HubSpot id: posting it back with `source: hubspot` and that id as `external_id` returns the TruTec activity with `"created": false` and changes nothing.
- **One HubSpot, several workspaces:** when several TruTec workspaces connect the same HubSpot account, every HubSpot change goes to each of them, and each only updates the jobs it already links by HubSpot id. A new deal is imported only by a workspace with "Import new HubSpot records into this workspace" on that owns it: the only such workspace, the one whose team includes the deal's HubSpot owner, or the only one using the deal's pipeline.

### Log an activity from another system

`POST /v1/activities` (Write jobs)

| Field | Required | Notes |
|---|---|---|
| `type` | Yes | `call`, `text`, `email`, `meeting`, `site_visit` or `note` |
| `body` | Yes, unless `subject` (or a call's `outcome`) is sent | What happened. Up to 8,000 characters. `content` is accepted as the same field |
| `associations` | Yes, at least one | `job_id`, `job_external_ref`, `client_account_id`, `client_account_external_ref`, `contact_id`, `lead_id`. All must be in this workspace (`404` otherwise) |
| `occurred_at` | No | ISO 8601 UTC. Default now. The activity sorts by it |
| `direction` | No | `inbound` or `outbound` |
| `subject` | No | Email subject or meeting title |
| `outcome` | No | Calls: `spoke`, `voicemail`, `no_answer`, `callback`, `wrong_number` (also `connected`, `left_voicemail`, `left_live_message`, `busy`) |
| `duration_seconds` | No | 0 to 86,400 |
| `source` | No | Default `api`. Your system's name, e.g. `hubspot` |
| `external_id` | No | Your id for it. With `source`, the dedupe key |
| `user_email` | No | The author, if they are a workspace member. Otherwise the API key's user, and the response says `"author_matched": false` |

Where it lands: with a job, on the job's activity (calls, texts, emails and meetings also on the customer's and the person's feed); with only a customer or a person, on their feed; a lead that has a job, customer or person logs there; a lead with none of those (a basic lead) keeps it on the lead, and an outbound call, text, email, meeting or site visit marks a New lead Contacted.

```bash
curl -X POST https://trutec.ai/api/v1/activities \
  -H "X-API-Key: $TRUTEC_API_KEY" -H "Content-Type: application/json" \
  -d '{"type": "email", "direction": "inbound", "occurred_at": "2026-10-16T14:20:00Z",
       "subject": "Re: Sealcoat quote", "body": "Can you start the week of the 26th?",
       "source": "hubspot", "external_id": "51234998801",
       "associations": {"job_external_ref": "deal-88412"}}'
```

Returns `201 {"data": {...activity...}, "created": true}`. Sending the same `source` + `external_id` again returns `200` with `"created": false` and updates the body, subject, outcome, duration, direction (and `occurred_at`, when sent) if they changed. The object is the one under [List activities](#list-activities).

### List activities

`GET /v1/activities` (Read jobs). Oldest first, one entry per interaction.

| Parameter | Notes |
|---|---|
| `type` | Comma list. Default `call,text,email,meeting,site_visit`; add `note` for notes |
| `source` | `trutec` = logged in TruTec; any other value = posted with that `source` |
| `updated_after` | Logged (or updated through this API) after this time |
| `job_id`, `contact_id`, `client_account_id`, `lead_id` | Only that record's activity. `lead_id` also returns entries kept on a basic lead |
| `limit`, `cursor` | Page size (default 50, max 200) and the previous page's `next_cursor` |

```json
{
  "data": [{
    "id": "ca_8812",
    "type": "call",
    "direction": "outbound",
    "subject": null,
    "body": "Spoke with them\nWants the price by Friday",
    "outcome": "spoke",
    "duration_seconds": 312,
    "call": null,
    "photos": [],
    "location": null,
    "condition": null,
    "occurred_at": "2026-10-16T15:04:00Z",
    "source": "hubspot",
    "external_id": "51234998877",
    "logged_via": "api",
    "user": {"id": 1204, "email": "sam@example.com", "name": "Sam Estimator", "hubspot_owner_id": "7781123"},
    "associations": {"job_id": 4812, "job_number": "J-1042", "job_name": "Northgate Plaza",
                     "job_external_ref": "deal-88412", "hubspot_deal_id": "88412",
                     "client_account_id": 915, "client_account_name": "Northgate Property Group",
                     "client_account_external_ref": "CUST-0091", "contact_id": 5521,
                     "contact_name": "Alex Rivera", "contact_email": "alex@example.com",
                     "contact_phone": "555-0142", "hubspot_contact_id": "1201",
                     "lead_id": null, "lead_external_ref": null},
    "job_activity_id": 990211,
    "contact_activity_id": 8812,
    "created_at": "2026-10-16T15:04:00Z"
  }],
  "next_cursor": "eyJ0IjoiMjAyNi0xMC0xNlQxNTowNDowMCIsInIiOjAsImkiOjg4MTJ9",
  "has_more": false
}
```

`call` is set on calls made or taken in TruTec (null for a call someone logged by hand or posted from another system):

| Field | Meaning |
|---|---|
| `status` | `ringing`, `in-progress`, `completed`, `no-answer`, `busy`, `failed`, `canceled` or `missed` (an inbound call nobody answered) |
| `answered` | `true` once the customer picked up |
| `from_number`, `to_number` | E.164, e.g. `+14155550142`. Outbound calls come from the estimator's own TruTec number |
| `ended_at` | When the call ended (UTC), or null |
| `has_recording`, `recording_seconds` | Workspaces that record calls. The recording plays in TruTec for signed-in users |
| `summary` | AI summary of a recorded call (a few bullets and a next step), or null |

**Site visits** logged in the field ("Log a site visit" on a phone, or the Site visit tab on a lead) also carry what was captured; other types return `[]` / `null`:

| Field | Meaning |
|---|---|
| `photos` | Links to the visit's photos |
| `location` | `{lat, lng, accuracy_m}` where it was logged (the phone's location, or the typed address when location was off), or null |
| `condition` | `good`, `fair`, `poor` or `needs_repair` ("Needs repair now"), or null |

Page until `has_more` is false and keep the last `next_cursor`: calling again with it later returns only what was logged since. `id` is an opaque, stable string (`ca_…` company/contact feed row, `ja_…` job line with no feed row, `la_…` entry on a basic lead).

## Customer history

### A contact's or customer's history

`GET /v1/contacts/{id}/timeline`

`GET /v1/client-accounts/{id}/timeline`

**Permission:** Read jobs

One newest-first feed of everything that happened with the person or company, as on their page in the app: emails and texts in and out, calls and meetings, notes, follow-ups set and done, job stage moves, new jobs, proposals sent / viewed / signed, e-signatures and invoices. It covers every job the person or company is on.

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` | integer | No | Default 50, maximum 100. |
| `before` | string | No | Cursor: the previous page's `next_before`. |
| `kinds` | string | No | Comma-separated groups: `messages`, `calls`, `notes`, `tasks`, `deals`, `money`. |
| `q` | string | No | Only entries mentioning this text (or on a job whose name, address or contact matches). |

```json
{
  "data": [
    {
      "key": "ca:email:55102",
      "kind": "email",
      "group": "messages",
      "at": "2026-10-11T18:22:09Z",
      "title": "Email to Jamie Rivera",
      "subject": "Reseal proposal",
      "body": "Attached is the proposal...",
      "actor": "Dana Whitfield",
      "chips": [],
      "links": [{"kind": "job", "label": "Maple Ridge HOA reseal", "url": "/jobs/41872"}]
    }
  ],
  "has_more": true,
  "next_before": "2026-10-11T18:22:09~ca:email:55102"
}
```

Page with `before` until `has_more` is false. Money entries (invoice amounts, deposits) appear only when the key's creator can see pricing.

## Job follow-ups

### The follow-up object

| Field | Type | Description |
|---|---|---|
| `id` | integer | TruTec id. |
| `job_id`, `job` | integer, object | The job (`{id, name}`). |
| `title`, `description` | string | What to do. |
| `task_type` | string | `follow_up`, `call`, `email`, `meeting`, `appointment`, `site_visit` or `other`. |
| `due_date` | string or null | UTC. |
| `timezone` | string | Time zone the reminder is shown in. |
| `reminder_minutes_before` | integer or null | Email reminder lead time. |
| `completed`, `completed_at`, `completion_notes` | | Done state and what happened. |
| `is_overdue` | boolean | Open and past due. |
| `assigned_to_user_id`, `assigned_to` | integer, object | Who does it. |
| `created_by_user_id`, `created_at` | | Who added it and when. |
| `external_ref` | object or null | `{source, external_id}` when it is linked to a task in another system (see **Syncing tasks from your CRM**); `null` when it lives only in TruTec. |

**Due times.** Send ISO 8601. With `Z` or an offset (`2026-10-20T15:30:00Z`) it is that exact time. Without one (`2026-10-20T10:30`) it is read in `timezone` (an IANA name such as `America/Chicago`), or the workspace's time zone. A bare date means 9:00 AM that day.

### List job follow-ups

`GET /v1/tasks`

**Permission:** Read jobs

Soonest due first; undated ones last.

| Name | Type | Required | Description |
|---|---|---|---|
| `status` | string | No | `open` (default), `completed` or `all`. |
| `job_id` | integer | No | One job. |
| `assigned_to_user_id` | integer | No | One assignee. |
| `due_before`, `due_after` | string | No | ISO 8601. |
| `limit`, `offset` | integer | No | Paging. |

### Get a job follow-up

`GET /v1/tasks/{id}`

**Permission:** Read jobs

### Create a job follow-up

`POST /v1/tasks`

**Permission:** Write jobs

| Name | Type | Required | Description |
|---|---|---|---|
| `job_id` | integer | Yes | The job. |
| `title` | string | Yes | What to do. |
| `task_type` | string | No | Default `follow_up`. |
| `description` | string | No | Details. |
| `due_date` | string | No | See **Due times**. |
| `timezone` | string | No | For a due time without an offset, and for the reminder. |
| `assigned_to_user_id` | integer | No | Default: the key's creator. |
| `reminder_minutes_before` | integer | No | Default 30 (60 for meetings and site visits) when there is a due time. |
| `source` | string | No | Your system's name, e.g. `hubspot`. With `external_id`, makes this POST an upsert. |
| `external_id` | string | No | Your id for the task, e.g. the HubSpot task id. |
| `completed` | boolean | No | Only with `source` + `external_id`: closes (or reopens) it. |

```bash
curl -X POST "https://trutec.ai/api/v1/tasks" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"job_id": 41872, "title": "Call Jamie back", "task_type": "call", "due_date": "2026-10-20T15:30:00Z"}'
```

```json
{
  "data": {
    "id": 7731,
    "job_id": 41872,
    "job": {"id": 41872, "name": "Maple Ridge HOA reseal"},
    "title": "Call Jamie back",
    "description": null,
    "task_type": "call",
    "due_date": "2026-10-20T15:30:00Z",
    "timezone": "America/Chicago",
    "reminder_minutes_before": 30,
    "completed": false,
    "completed_at": null,
    "completion_notes": null,
    "is_overdue": false,
    "assigned_to_user_id": 118,
    "assigned_to": {"id": 118, "name": "Dana Whitfield"},
    "created_by_user_id": 118,
    "created_at": "2026-10-12T14:05:00Z"
  }
}
```

A "Task created" line is added to the job's activity.

**Errors:** `400` `job_id is required`, `title is required`, `task_type must be one of: ...`, `due_date must be an ISO 8601 date or date-time`, `source and external_id go together`; `404` for a job or user outside the workspace; `409` `external_id_in_use` when that `source` + `external_id` is a follow-up on another job or a company follow-up.

### Syncing tasks from your CRM

Send `source` + `external_id` on `POST /v1/tasks` or `POST /v1/account-tasks` and the POST becomes an idempotent upsert keyed on them:

- The first POST creates the follow-up and links it: `201` with `"created": true` and `external_ref` set.
- The same `source` + `external_id` again updates that follow-up with the fields you send (title, notes, type, due time, assignee, `completed`) and returns `200` with `"created": false`. Posting the same body twice, or two at once, never makes a second follow-up.
- `completed: true` closes it the quiet way: no call, email or meeting is logged for it, because it happened in your system.
- **No echoes.** A change made through the API to a linked follow-up (POST, `PATCH`, `/complete`) is treated as coming from that system and is never sent back to it.
- **Built-in HubSpot tasks.** A workspace connected on Settings → HubSpot can switch on "Show HubSpot tasks in TruTec": each person's HubSpot tasks (people matched to HubSpot owners) show in their My day, on the deal's job or the company, tagged HubSpot; checking one off in TruTec completes it in HubSpot, and a reschedule moves its due date. Those follow-ups carry `external_ref` `{"source": "hubspot", "external_id": "<HubSpot task id>"}`, the same key a workflow posting HubSpot tasks with `source: hubspot` uses, so the two never make the task twice.

```bash
curl -X POST "https://trutec.ai/api/v1/tasks" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"job_id": 41872, "title": "Call Jamie back", "task_type": "call", "due_date": "2026-10-20T15:30:00Z", "source": "hubspot", "external_id": "88812345901"}'
```

### Update a job follow-up

`PATCH /v1/tasks/{id}`

**Permission:** Write jobs

Send any of `title`, `description`, `task_type`, `due_date` (null clears it), `timezone`, `assigned_to_user_id`, `reminder_minutes_before`, `completion_notes` and `completed`. Rescheduling sends the reminder again. `completed: true` completes it the way the task window does: with `completion_notes`, they post to the job (a call task logs a call, a meeting task a meeting) and to the customer's feed; without notes a "Task completed" line is added. `completed: false` reopens it.

### Complete a job follow-up

`POST /v1/tasks/{id}/complete`

**Permission:** Write jobs

Shortcut for completing with `notes` (optional) as the completion notes. A follow-up that is already done is returned unchanged.

```bash
curl -X POST "https://trutec.ai/api/v1/tasks/7731/complete" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Booked a walk-through for Friday"}'
```

### Delete a job follow-up

`DELETE /v1/tasks/{id}`

**Permission:** Write jobs and Delete records

Returns `204`.

## Company follow-ups

Follow-ups on a customer when there is no job ("call the board back after their meeting"). They can repeat: completing one opens the next on schedule.

### The company follow-up object

Like the job follow-up, with `client_account_id` / `client_account` instead of the job, plus:

| Field | Type | Description |
|---|---|---|
| `contact_id` | integer or null | The person to reach. |
| `repeat` | object or null | `{every, unit, label}`, e.g. `{"every": 3, "unit": "month", "label": "Every 3 months"}`. |
| `completed_by_user_id` | integer or null | Who completed it. |
| `source` | string | `manual`, `my_day`, `repeat`, `crm_intake:<lead id>` (a lead's "Call new lead"), `automation:<rule id>`, `hubspot:task:<id>` (a HubSpot task), ... |
| `external_ref` | object or null | `{source, external_id}` when linked to a task in another system. |

`task_type` is one of `follow_up`, `call`, `email`, `meeting`, `site_visit`, `other`.

### List company follow-ups

`GET /v1/account-tasks`

**Permission:** Read jobs

Filters: `status` (`open` default, `completed`, `all`), `client_account_id`, `contact_id`, `assigned_to_user_id`, `due_before`, `due_after`, `limit`, `offset`.

### Get a company follow-up

`GET /v1/account-tasks/{id}`

**Permission:** Read jobs

### Create a company follow-up

`POST /v1/account-tasks`

**Permission:** Write jobs

| Name | Type | Required | Description |
|---|---|---|---|
| `client_account_id` | integer | Yes | The customer (not archived). |
| `title` | string | No | Default: "Follow up · Maple Ridge HOA". |
| `task_type` | string | No | Default `follow_up`. |
| `description` | string | No | Details. |
| `due_date`, `timezone` | string | No | See **Due times**. |
| `contact_id` | integer | No | One of the customer's people. |
| `assigned_to_user_id` | integer | No | An office user. Default: the key's creator. |
| `repeat_every`, `repeat_unit` | integer, string | No | Repeat every N `day`s, `week`s or `month`s (1-365). |
| `source`, `external_id` | string | No | Together: an upsert keyed on them (see **Syncing tasks from your CRM**). |
| `completed` | boolean | No | Only with `source` + `external_id`. |

```bash
curl -X POST "https://trutec.ai/api/v1/account-tasks" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"client_account_id": 4812, "title": "Quarterly check-in", "task_type": "call", "due_date": "2026-11-02T16:00:00Z", "repeat_every": 3, "repeat_unit": "month"}'
```

Returns `201` with `{"data": {company follow-up}}`. With `source` + `external_id`, `"created"` says whether it was new (`200` when it already existed); `409` `external_id_in_use` when the key is another customer's follow-up or a job follow-up.

### Update a company follow-up

`PATCH /v1/account-tasks/{id}`

**Permission:** Write jobs

Send any of `title`, `task_type`, `description`, `due_date`, `timezone`, `contact_id`, `assigned_to_user_id` (null unassigns), `repeat_every` / `repeat_unit` (null stops repeating). **Errors:** `409` `already_completed`.

### Complete a company follow-up

`POST /v1/account-tasks/{id}/complete`

**Permission:** Write jobs

Marks it done, as on My day: a call, email, meeting or site visit is logged on the company feed (with `notes`) and counts as a touch; another kind with `notes` adds a company note. A repeating follow-up opens the next one, returned as `next_task`.

```json
{
  "data": {"id": 3305, "completed": true, "completion_notes": "Happy with the work", "...": "..."},
  "next_task": {"id": 3340, "title": "Quarterly check-in", "due_date": "2027-02-02T16:00:00Z", "repeat": {"every": 3, "unit": "month", "label": "Every 3 months"}, "...": "..."}
}
```

### Delete a company follow-up

`DELETE /v1/account-tasks/{id}`

**Permission:** Write jobs and Delete records

Deletes the follow-up, open or done, for good (a repeating one stops repeating), as its delete button in the app does. Returns `204`. Workspaces where the app has no delete for company follow-ups get `403` with `code: not_available`; complete the follow-up instead.

## Changing or removing notes

Notes, logged calls, emails, meetings and site visits can't be edited or deleted, in the app or through the API: they are the record of what happened. To correct one, add another note.
