Skip to content
TruTecDevelopers
Get an API key

Leads

Send new requests into the lead inbox from your website or other systems, and read, assign, mark contacted, move forward or dismiss them.

View as Markdown

The lead inbox holds new requests waiting for someone to reach out: website request forms, customer-portal requests and requests sent through the API.

When a lead comes in, TruTec runs the same intake as a web request form:

  1. matches the person and company to records you already have, or creates them;
  2. opens a job in the first open stage of your sales pipeline (and links the site from the address);
  3. routes it to a teammate using your lead routing settings (round-robin, territory and service rules);
  4. creates a "Call new lead" company follow-up due within your speed-to-lead window;
  5. emails the assigned teammate (you can turn this off per request).

Nothing is sent to the lead.

In TruTec Heavy workspaces a website request form can instead add a basic lead to the Leads pipeline (Request forms → Routing → "Where website requests go"): no company, contact or job until someone moves it to sales. Leads sent through this API always get the full intake above.

A lead's status is new, contacted, converted ("Moving forward"), dismissed or spam. TruTec updates it on its own: a logged call, email or meeting with the person (or the "Call new lead" follow-up being completed) makes it contacted, and the job leaving its first stage makes it Moving forward.

Note

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

The lead object#

Field Type Description
id integer TruTec id.
status, status_label string See above. converted reads "Moving forward".
name, email, phone, company, address string or null What the person sent.
services string or null Requested services, as readable labels.
source string or null Form name, "Customer portal", or your source label.
source_kind string form, portal, api or manual.
submitted_at string When it came in (UTC).
assigned_to_user_id, assigned_to integer, object Who it went to ({id, name}).
assignment_reason string or null Why it went to them.
first_contacted_at, first_contact_kind, first_contacted_by_user_id When and how someone first reached out (call, email, text, meeting, task, marked).
contact_id, client_account_id, job_id, location_id integer or null The records the lead created or matched.
follow_up_task_id integer or null The "Call new lead" company follow-up (GET /v1/account-tasks/{id}).
answers array [{label, value}], everything they told you.
utm object Campaign fields sent with the request.
match_reasons array Why an existing contact or company was matched.
created_records object Which records this lead created (contact, account, job, location).
escalated_at string or null When the owner was alerted that nobody had reached out.
auto_spam boolean The form flagged it as spam on its own.
problem string or null Set when the request was saved but its job could not be created.
on_leads_pipeline boolean TruTec Heavy: the lead waits on the Leads pipeline with no company or job yet (typed in with "Add lead", or a website request). Someone moves it to sales in TruTec, where the company is added; until then convert answers 409 no_job.
pipeline_stage_id integer or null Its Leads pipeline stage.
estimated_value, customer_type, notes, next_follow_up_at What the team typed on a Leads pipeline lead.
status_changed_at, created_at, updated_at string UTC timestamps with Z.

Leads#

List leads#

GET /v1/leads

Permission: Read jobs

Newest first. With updated_after, oldest change first.

Name Type Required Description
status string No all (default), open (new + contacted), new, contacted, converted, dismissed or spam.
assigned_to_user_id integer No Only this teammate's leads.
source_kind string No form, portal, api or manual.
q string No Name, email, phone, company or address contains.
updated_after string No ISO 8601.
limit, offset integer No Paging (default 50, max 200).
curl "https://trutec.ai/api/v1/leads?status=open" \
  -H "X-API-Key: sk_your_api_key_here"
{
  "data": [
    {
      "id": 902,
      "status": "new",
      "status_label": "New",
      "name": "Jordan Ellis",
      "display_name": "Jordan Ellis",
      "email": "[email protected]",
      "phone": "(512) 555-0199",
      "company": "Lakeside Commons",
      "address": "400 Lake Rd, Austin, TX 78701",
      "services": "Sealcoating",
      "source": "API",
      "source_kind": "api",
      "submitted_at": "2026-10-12T14:00:00Z",
      "assigned_to_user_id": 118,
      "assigned_to": {"id": 118, "name": "Dana Whitfield"},
      "assignment_reason": "Round-robin",
      "first_contacted_at": null,
      "first_contact_kind": null,
      "first_contacted_by_user_id": null,
      "contact_id": 20500,
      "client_account_id": 4901,
      "job_id": 41901,
      "location_id": 9411,
      "follow_up_task_id": 3310,
      "answers": [{"label": "Name", "value": "Jordan Ellis"}, {"label": "Services", "value": "Sealcoating"}],
      "utm": {},
      "match_reasons": [],
      "created_records": {"contact": true, "account": true, "job": true},
      "escalated_at": null,
      "auto_spam": false,
      "problem": null,
      "status_changed_at": null,
      "created_at": "2026-10-12T14:00:00Z",
      "updated_at": "2026-10-12T14:00:00Z"
    }
  ],
  "pagination": {"total": 1, "limit": 50, "offset": 0}
}

Get a lead#

GET /v1/leads/{id}

Permission: Read jobs

Returns {"data": {lead}}. Errors: 404 Lead not found.

Create a lead#

POST /v1/leads

Permission: Write jobs

Use this for requests from your own website or another system. If you are syncing jobs you already have, use POST /v1/jobs instead (see Jobs); those are not new leads.

Name Type Required Description
name string Yes Who is asking.
email string One of At least an email or a phone.
phone string One of 10 to 15 digits with area code.
company string No Company or property name.
address string No Property address.
message string No What they need. Up to 3,000 characters.
services array of strings No Job-type values from GET /v1/job-types, e.g. ["sealcoating", "striping"].
timing string No asap, 30_days, this_season, budgeting or unsure.
property_type string No One of the workspace's property types.
source string No Label shown in the inbox, e.g. "Spring mailer". Default API.
utm object No Campaign fields such as utm_source, utm_campaign, gclid.
notify boolean No Email the assigned teammate the new-lead alert. Default true.
curl -X POST "https://trutec.ai/api/v1/leads" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jordan Ellis",
    "email": "[email protected]",
    "phone": "(512) 555-0199",
    "company": "Lakeside Commons",
    "address": "400 Lake Rd, Austin, TX 78701",
    "services": ["sealcoating"],
    "message": "Lot needs a reseal before spring"
  }'

Returns 201 with {"data": {lead}}. If something fails part-way, the lead is still saved with its answers and problem is set, so nobody loses a request.

Errors: 400 name is required, Send an email or a phone number so someone can reach them., Unknown services: ..., a bad timing or property_type, or unknown fields.

Mark a lead contacted#

POST /v1/leads/{id}/contacted

Permission: Write jobs

Someone reached out. A call, email or meeting is logged on the job and the company feed (with note); a text is noted on the person. The "Call new lead" follow-up closes.

Name Type Required Description
how string No call, email, text, meeting or other (default).
note string No What happened.

Returns {"data": {lead}}. Errors: 409 Restore this lead first. for a dismissed or spam lead.

Assign a lead#

POST /v1/leads/{id}/assign

Permission: Write jobs

Hands the lead, its job and its open follow-up to user_id (an office teammate). A new lead that hasn't been contacted yet emails them the new-lead alert. Returns {"data": {lead}}.

Errors: 400 user_id is required; 404 when the user is not an office teammate in the workspace.

Move a lead forward#

POST /v1/leads/{id}/convert

Permission: Write jobs

Every lead already has a job. This moves that job to a later stage, with the same rules as POST /v1/jobs/{id}/move (see Jobs), and the inbox then shows the lead as "Moving forward".

Name Type Required Description
status string No An open or won stage key from GET /v1/job-stages. Default: the next open stage after the one the lead came in at.
confirm_schedule_completion boolean No Needed when the move would complete open work orders.
curl -X POST "https://trutec.ai/api/v1/leads/902/convert" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"status": "bid_sent"}'

Returns {"data": {lead}, "job": {job}}.

Errors: 400 for a lost or excluded stage (close a lead with /dismiss, or move its job with /v1/jobs/{id}/move), or when there is no later open stage; 409 lead_closed, no_job or confirmation_required; 403 status_locked.

Dismiss a lead#

POST /v1/leads/{id}/dismiss

Permission: Write jobs

Takes the lead out of the inbox. Send {"spam": true} to mark it spam instead: the contact and company the lead created are archived too, when nothing else uses them. Its follow-up closes. Returns {"data": {lead}, "message": "..."}.

Restore a lead#

POST /v1/leads/{id}/restore

Permission: Write jobs

Back into the inbox, as new or (if someone already reached out) contacted; anything archived by marking it spam comes back. A submission the form flagged as spam on its own has no job yet: it runs through the full intake now and returns as a new lead, with replaced_lead_id naming the old one.

Edit or delete a lead#

A lead is the request as it came in, so it can't be edited or deleted, in the app or through the API. Change what it created instead: the person (PATCH /v1/contacts/{id}), the company (PATCH /v1/client-accounts/{id}) or the job (PATCH /v1/jobs/{id}), using the contact_id, client_account_id and job_id on the lead. To take a lead out of the inbox, dismiss it (or mark it spam).