Skip to content
TruTecDevelopers
Get an API key

Contacts

Read and write the people you work with, their phones and emails, their roles at customers and sites, and who is on each job.

View as Markdown

A contact is a person: a property manager, an HOA board member, a facilities director, an accounts-payable clerk. Each contact can have:

  • a primary company (client_account_id, see Customers & sites);
  • several phone numbers and emails (channels), one primary of each kind;
  • roles at other companies and sites (links), with start and end dates so history survives a handoff;
  • preferences (preferred channel, do-not-contact, birthday, tags) and the workspace's custom fields (see Custom fields).

A job has a list of people on the job. The primary person fills the job's own contact_name, contact_email and contact_phone. Extra people can be marked as additional customers who get their own proposal copy.

Note

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

Response format#

Lists return {"data": [...], "pagination": {"total", "limit", "offset"}}. limit defaults to 50 (maximum 200). Single records return {"data": {...}}. Timestamps are UTC ISO 8601 with a Z (2026-10-12T14:03:22Z); dates are YYYY-MM-DD. Errors are {"error": "...", "code": "..."}; unknown body fields are a 400 that lists them in fields.

The contact object#

Field Type Description
id integer TruTec id.
name string or null Full name.
display_name string Name, or company when there is no name.
title string or null Job title.
contact_type string or null The workspace's own category (Property Manager, Billing, ...).
company string or null Company name shown for the person.
client_account_id integer or null Primary company.
client_account object or null {id, name} of that company.
email, phone string or null The primary email and phone.
notes string or null The pinned note on the person.
assigned_user_id integer or null Salesperson for this person. null means the company's salesperson.
preferred_channel string or null call, text or email.
do_not_contact, do_not_contact_reason boolean, string Don't call, text or email this person.
birthday string or null YYYY-MM-DD.
tags array of strings Free tags.
custom_fields object Custom field values by key.
archived_at, created_at, updated_at string or null UTC timestamps.

GET /v1/contacts/{id} and every write also return:

Field Description
channels Every phone and email: id, ref, kind (phone / email), label (mobile, work, office, home, fax, other), value, is_primary, do_not_contact.
links Companies and sites: kind (company / location), id, name, roles, is_primary, current, started_on, ended_on.
jobs Jobs the person is on: job_id, name, status, role, is_primary, bills_separately.

Contacts#

List contacts#

GET /v1/contacts

Permission: Read jobs

People A to Z. With updated_after, oldest change first, so you can page forward with the last row's updated_at.

Name Type Required Description
q string No Name, email, phone, company or title contains this text.
email string No Exact email, any case. Matches any of the person's emails, so use it to look someone up before creating them.
client_account_id integer No People whose primary company this is. For everyone with a role there, use GET /v1/client-accounts/{id}/contacts.
job_id integer No People on this job.
contact_type string No Exact category.
include_archived boolean No Include archived people.
updated_after string No ISO 8601.
limit, offset integer No Paging.
curl "https://trutec.ai/api/v1/[email protected]" \
  -H "X-API-Key: sk_your_api_key_here"
{
  "data": [
    {
      "id": 20417,
      "name": "Jamie Rivera",
      "display_name": "Jamie Rivera",
      "title": "Community manager",
      "contact_type": "Property Manager",
      "company": "Maple Ridge HOA",
      "client_account_id": 4812,
      "client_account": {"id": 4812, "name": "Maple Ridge HOA"},
      "email": "[email protected]",
      "phone": "(512) 555-0142",
      "notes": null,
      "assigned_user_id": null,
      "preferred_channel": "text",
      "do_not_contact": false,
      "do_not_contact_reason": null,
      "birthday": null,
      "tags": ["board"],
      "custom_fields": {},
      "archived_at": null,
      "created_at": "2026-09-14T15:30:02Z",
      "updated_at": "2026-10-12T14:03:22Z"
    }
  ],
  "pagination": {"total": 1, "limit": 50, "offset": 0}
}

Errors: 403 API key lacks job read permission; 400 for a bad updated_after.

Get a contact#

GET /v1/contacts/{id}

Permission: Read jobs

One person (archived or not) with channels, links and jobs.

{
  "data": {
    "id": 20417,
    "name": "Jamie Rivera",
    "company": "Maple Ridge HOA",
    "client_account_id": 4812,
    "email": "[email protected]",
    "phone": "(512) 555-0142",
    "channels": [
      {"id": 5521, "ref": "5521", "kind": "phone", "label": "mobile", "value": "(512) 555-0142", "is_primary": true, "do_not_contact": false},
      {"id": 5522, "ref": "5522", "kind": "email", "label": "work", "value": "[email protected]", "is_primary": true, "do_not_contact": false}
    ],
    "links": [
      {"kind": "company", "id": 4812, "name": "Maple Ridge HOA", "roles": ["property_manager"], "is_primary": true, "current": true, "started_on": null, "ended_on": null},
      {"kind": "location", "id": 9307, "name": "Clubhouse lot", "roles": ["site_contact"], "is_primary": false, "current": true, "started_on": "2025-03-01", "ended_on": null}
    ],
    "jobs": [
      {"job_id": 41872, "name": "Maple Ridge HOA reseal", "status": "lead", "role": "billing", "is_primary": true, "bills_separately": false}
    ]
  }
}

The example is shortened; every field of the contact object is present.

Errors: 404 Contact not found (also for another workspace's contact).

Create a contact#

POST /v1/contacts

Permission: Write jobs

Send at least one of name, company, email or phone.

Name Type Required Description
name string No Max 200 characters.
title string No Job title.
contact_type string No Category. Max 80 characters.
company string No Company name. Linked to the customer with that name, or a new customer is created, as in the app.
client_account_id integer No Primary company by id. Wins over company.
email, phone string No Primary email and phone. More can be added with Add a phone or email.
notes string No The pinned note.
assigned_user_id integer No Salesperson for this person (an office user).
preferred_channel string No call, text or email.
do_not_contact boolean No Don't reach out. Add do_not_contact_reason.
birthday string No YYYY-MM-DD.
tags array of strings No Free tags.
custom_fields object No Values by field key.

Contacts have no external_ref. To avoid duplicates, look the person up first with GET /v1/contacts?email=.

curl -X POST "https://trutec.ai/api/v1/contacts" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jamie Rivera",
    "email": "[email protected]",
    "phone": "(512) 555-0142",
    "client_account_id": 4812,
    "title": "Community manager",
    "preferred_channel": "text"
  }'

Returns 201 with {"data": {contact with channels, links and jobs}}.

Errors:

  • 400 Add at least a name, company, email, or phone number., email does not look like an email address, Pick call, text or email., an unknown or invalid custom field, or unknown body fields
  • 404 client_account_id not found in this workspace, assigned_user_id does not match a user in this workspace

Update a contact#

PATCH /v1/contacts/{id}

Permission: Write jobs

Takes the same fields as create; only the ones you send change. client_account_id: null removes the primary company. When the name, email, phone or company changes, every job the person is on picks up the new details, the same as saving the contact in the app.

curl -X PATCH "https://trutec.ai/api/v1/contacts/20417" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"phone": "(512) 555-0100", "tags": ["board", "billing"]}'

Returns {"data": {contact}}. Same errors as create.

Archive or delete a contact#

DELETE /v1/contacts/{id}

Permission: Write jobs and Delete records

Archives the person (204). They drop out of lists but keep their history; POST /v1/contacts/{id}/restore brings them back.

Add ?hard=true to delete permanently. Jobs keep their own contact fields. The person's own notes and files are deleted; notes that are also on their company's feed stay there.

Errors: 403 API key lacks delete permission; 403 with "deletion_locked": true when the workspace has turned off deleting records; 404.

Restore a contact#

POST /v1/contacts/{id}/restore

Permission: Write jobs

Un-archives the person. Returns {"data": {contact}}.

Phones and emails#

The primary phone and email of each person are also their phone and email fields. Editing those fields moves the primary entry; these endpoints manage the full list.

Each entry has a ref. For a person whose phones and emails have never been edited, entries are read from the phone / email text and have id: null and a ref like v-phone-0; the first edit saves them all and gives them ids.

Add a phone or email#

POST /v1/contacts/{id}/channels

Permission: Write jobs

Name Type Required Description
kind string Yes phone or email.
value string Yes The number or address. Phone numbers are formatted for you.
label string No mobile, work, office, home, fax or other.
is_primary boolean No Make it the primary of its kind. The first of each kind is primary.
curl -X POST "https://trutec.ai/api/v1/contacts/20417/channels" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"kind": "phone", "value": "512-555-0177", "label": "mobile"}'
{"data": {"id": 5530, "ref": "5530", "kind": "phone", "label": "mobile", "value": "(512) 555-0177", "is_primary": false, "do_not_contact": false}}

Errors: 400 kind must be phone or email, That email doesn’t look right., Type the whole phone number.; 409 That one is already on file.

Update a phone or email#

PATCH /v1/contacts/{id}/channels/{ref}

Permission: Write jobs

Send any of value, label, is_primary (true makes it primary) and do_not_contact. Returns {"data": {channel}}.

Remove a phone or email#

DELETE /v1/contacts/{id}/channels/{ref}

Permission: Write jobs

Removes one entry (204). If it was primary, the next one of that kind becomes primary. This is an edit of the contact, so it needs only Write jobs.

Roles at companies and sites#

Set a role at a company or site#

POST /v1/contacts/{id}/links

Permission: Write jobs

Records that the person works with a company or a site. Sending the same company or site again replaces the roles.

Name Type Required Description
client_account_id integer One of The company.
location_id integer One of The site.
roles array No Any of decision_maker, property_manager, site_contact, billing, accounts_payable, board_member, other. Empty means "works with them".
started_on string No YYYY-MM-DD.
is_primary boolean No Make this their primary company (or main site). A person with no primary company gets the first company linked.
curl -X POST "https://trutec.ai/api/v1/contacts/20417/links" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"location_id": 9307, "roles": ["site_contact"]}'

Returns 201 with {"data": [links]}: all of the person's links.

Errors: 400 Send exactly one of client_account_id or location_id, unknown roles; 404 for a company or site outside the workspace.

End or remove a role#

DELETE /v1/contacts/{id}/links/{kind}/{target_id}

Permission: Write jobs

kind is company or location. With ?end=true the link is kept as history ("no longer works there", ended today or on ended_on=YYYY-MM-DD); without it the link is removed. Ending or removing their primary company picks another current company, or none. Returns 204.

People on a job#

The people on a job, with their role on it, is_primary and bills_separately.

List the people on a job#

GET /v1/jobs/{job_id}/contacts

Permission: Read jobs

Primary first, then by role.

{
  "data": [
    {
      "contact_id": 20417,
      "job_id": 41872,
      "role": "billing",
      "is_primary": true,
      "bills_separately": false,
      "created_at": "2026-10-12T14:03:22Z",
      "contact": {"id": 20417, "name": "Jamie Rivera", "display_name": "Jamie Rivera", "title": "Community manager", "contact_type": "Property Manager", "email": "[email protected]", "phone": "(512) 555-0142", "client_account_id": 4812, "archived_at": null, "updated_at": "2026-10-12T14:03:22Z"}
    }
  ]
}

Add a person to a job#

POST /v1/jobs/{job_id}/contacts

Permission: Write jobs

Link a saved person with contact_id, or create one by sending name / email / phone / company (and optionally title, contact_type). The first person on a job becomes primary and fills the job's contact fields; the job is linked to the person's company if it has none.

Name Type Required Description
contact_id integer One of A saved, non-archived person.
name, email, phone, company, title, contact_type string One of A new person (a name or an email at least).
role string No billing, onsite, decision_maker, property_manager, estimator, operations, or your own text.
is_primary boolean No Make them the job's primary contact.
bills_separately boolean No Add them as an additional customer: the job is bid to them too and each gets a proposal copy. The job must already have a main customer.
curl -X POST "https://trutec.ai/api/v1/jobs/41872/contacts" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"contact_id": 20417, "role": "billing"}'

Returns 201 with {"data": {person on the job}}, or 200 when the person was already on the job.

Errors: 400 Archived contacts cannot be linked to jobs. Restore it first., Add the job’s main customer first, then add more customers.; 404 for a job or contact outside the workspace.

Change a person's role on a job#

PATCH /v1/jobs/{job_id}/contacts/{contact_id}

Permission: Write jobs

Send role, is_primary: true (makes them primary; the job's contact fields follow), or bills_separately (turn "additional customer" on or off; the job's activity log records it). Returns {"data": {person on the job}}.

Remove a person from a job#

DELETE /v1/jobs/{job_id}/contacts/{contact_id}

Permission: Write jobs

Unlinks the person; the contact itself stays. If they were primary, the next person becomes primary and fills the job's contact fields. Returns 204.

Notes and history#

A person's notes feed and their full history are in Notes & tasks: GET /v1/contacts/{id}/notes, POST /v1/contacts/{id}/notes and GET /v1/contacts/{id}/timeline.