Notes & tasks
Write notes and log calls, emails and meetings on jobs, contacts and customers; sync activity with another CRM; read activity logs and customer history; create and complete follow-ups.
View as MarkdownThree 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. |
{
"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. |
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.
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"}'
{
"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/activitieslogs a call, text, email, meeting, site visit or note that happened in your CRM, on the job and the customer's feed, keyed on yoursource+external_id. - Out of TruTec: the
activity_loggedwebhook fires once for every call, text, email, meeting or site visit logged in TruTec.GET /v1/activitiesreturns the same objects for a backfill. - No echoes: anything you post with a
sourceother thantrutecis never sent back out asactivity_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_updatedfollows as it ends, as the outcome is saved and as an AI summary arrives. See thecallobject 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 withsource: hubspotand that id asexternal_idreturns the TruTec activity with"created": falseand 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.
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#
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 |
{
"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": "[email protected]", "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": "[email protected]",
"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). |
{
"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. |
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"}'
{
"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:
201with"created": trueandexternal_refset. - The same
source+external_idagain updates that follow-up with the fields you send (title, notes, type, due time, assignee,completed) and returns200with"created": false. Posting the same body twice, or two at once, never makes a second follow-up. completed: truecloses 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 withsource: hubspotuses, so the two never make the task twice.
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.
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 days, weeks or months (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. |
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.
{
"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.