# Build on TruTec

The TruTec API gives your own systems the same data your team sees in the app. Everything is JSON over HTTPS, authenticated with a workspace API key, and versioned under `/v1`.

<div class="dv-cards" markdown="0">
  <a class="dv-card" href="/developers/measure-a-site"><strong>Measure a site</strong><span>Send an address or an outline. Get pavement area, stalls, striping footage, crosswalks and more.</span></a>
  <a class="dv-card" href="/developers/jobs"><strong>Customers, sites &amp; jobs</strong><span>Read and write the customers, sites and jobs in your workspace, and sync them with your own database.</span></a>
  <a class="dv-card" href="/developers/estimates-and-proposals"><strong>Estimates &amp; proposals</strong><span>Line items, totals, versions and status for every estimate your team builds.</span></a>
  <a class="dv-card" href="/developers/webhooks-and-events"><strong>Webhooks &amp; events</strong><span>Get told when jobs, estimates and invoices change instead of polling.</span></a>
  <a class="dv-card" href="/developers/exports"><strong>File exports</strong><span>Get any file the app downloads (proposal PDFs, workbooks, payroll, takeoff maps and KMZ) as a 24-hour link.</span></a>
  <a class="dv-card" href="/developers/bulk-export"><strong>Bulk data export</strong><span>Download your workspace data in bulk, any time, for your warehouse or your records.</span></a>
  <a class="dv-card" href="/developers/mcp"><strong>AI assistants (MCP)</strong><span>Connect ChatGPT, Claude, Cursor, VS Code or any MCP client to your TruTec data.</span></a>
</div>

## The basics

| | |
|---|---|
| Base URL | `https://trutec.ai/api/v1` |
| Auth | Your API key in the `X-API-Key` header ([Authentication](authentication.md)) |
| Format | JSON request and response bodies, UTF-8. Send `Content-Type: application/json` on `POST` and `PATCH` |
| Transport | HTTPS only |
| Versioning | Breaking changes only ship in a new version ([Versioning & changes](versioning.md)) |
| Where to call from | Your servers. Keys are secrets and must never ship in browser or mobile code |
| Machine-readable | [OpenAPI 3.1 spec](/developers/openapi.yaml) · [llms.txt](/developers/llms.txt) for AI tools |

## Quick start

**1. Create a key.** A workspace owner opens **Settings › API keys** in TruTec and creates a key. Copy it right away: it is shown once.

**2. Check the connection.** `/v1/health` needs no key, so it tells you the API is reachable:

```bash
curl https://trutec.ai/api/v1/health
```

```json
{"status": "ok", "version": "v1"}
```

**3. Make an authenticated call.** List the jobs in your workspace:

```bash
curl https://trutec.ai/api/v1/jobs \
  -H "X-API-Key: sk_your_api_key_here"
```

**4. Measure a site.** Start a measurement from an address, then poll for the result:

```bash
curl -X POST https://trutec.ai/api/v1/detect/address \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"address": "1200 Commerce Dr, Austin, TX 78701"}'
```

The full flow, with polling and the result format, is in [Measure a site](measure-a-site.md).

## What's in the API

| Area | What you can do |
|---|---|
| [Measure a site](measure-a-site.md) | AI takeoff from aerial imagery: pavement, stalls, striping, crosswalks, markings |
| [Saved measurements & imagery](measurements.md) | Read takeoffs as your team saved them in the app, plus their imagery |
| [Condition ratings](condition-ratings.md) | PASER and PCI pavement condition ratings from one image |
| [Customers & sites](customers-and-sites.md) | Customer companies and the properties you work on |
| [Jobs](jobs.md) | Create, read and update jobs, with photos, change orders and proposal maps |
| [Estimates & proposals](estimates-and-proposals.md) | Estimates with line items, totals, versions and revisions |
| [Job costing](job-costing.md) | Costs, labor, invoices and allocation per job |
| [Webhooks & events](webhooks-and-events.md) | Change notifications and a replayable event feed |
| [Bulk data export](bulk-export.md) | Your workspace data in bulk files |
| [AI assistants (MCP)](mcp.md) | A hosted MCP server over the same API |
| [Partner API](partner-api.md) | For software platforms that offer TruTec to their own customers |

## Required fields by object

A create call without a required field is a `400` whose message names what is missing.

| Object | Create endpoint | Required | Notes |
|---|---|---|---|
| [Customer](customers-and-sites.md) (client account) | `POST /v1/client-accounts` | `name` | Takes `external_ref`. A repeat that matches a customer by `external_ref`, then by name, returns it (`200`, `created: false`) and only fills its blank fields; an archived match is restored. |
| [Site](customers-and-sites.md) (location) | `POST /v1/locations` | `address` | Takes `external_ref`. A repeat that matches a site by `external_ref`, then by address, returns it (`200`, `created: false`). When the workspace requires a company on sites, a new site also needs `client_account_id` (`422` without it). |
| [Contact](contacts.md) | `POST /v1/contacts` | At least one of `name`, `company`, `email`, `phone` (a `client_account_id` counts as the company) | No `external_ref`: each call adds a person. |
| [Job](jobs.md) | `POST /v1/jobs` | `name` | Takes `external_ref`: a job already carrying it is updated with the `PATCH` rules (`200`, `created: false`), and `name` can then be left out. Otherwise a job with the same name is a `409` with `duplicate_job_id`, unless `allow_duplicate_name` is `true`. A workspace can require more fields on new jobs (`422` with `missing_fields`). |
| [Task](notes-and-tasks.md) (job follow-up) | `POST /v1/tasks` | `job_id`, `title` | Each call adds a follow-up. |
| [Account task](notes-and-tasks.md) (customer follow-up) | `POST /v1/account-tasks` | `client_account_id` | `title` defaults to the follow-up type and customer name. |
| [Note or logged call](notes-and-tasks.md) | `POST /v1/jobs/{job_id}/notes`, `/v1/contacts/{contact_id}/notes`, `/v1/client-accounts/{account_id}/notes` | `content` | `type` `call`, `email` or `meeting` logs an interaction instead of a note. Each call adds one. |
| [Estimate](estimates-and-proposals.md) | `POST /v1/estimates` | `job_id` | |
| [Change order](change-orders.md) | `POST /v1/change-orders` | `job_id`, plus `title` when `kind` is `quick` (the default) | A change-order estimate can also start from an estimate with `POST /v1/estimates/{estimate_id}/change-orders` (nothing required). |
| [Invoice](invoices-and-payments.md) | `POST /v1/invoices` | `job_id`, `line_items` (at least one, each with `description` and an `amount` above 0) | Creates a draft; nothing is emailed. |
| [Webhook subscription](webhooks-and-events.md) | `POST /v1/webhooks` | `target_url` | `event_type` defaults to `job_status_changed`. Each call adds a subscription. |
| [Activity](notes-and-tasks.md) (call, text, email, meeting from another system) | `POST /v1/activities` | `type`, `body` (or `subject`; a call may send only `outcome`), and at least one of `associations.job_id`, `associations.job_external_ref`, `associations.client_account_id`, `associations.client_account_external_ref`, `associations.contact_id`, `associations.lead_id` | Idempotent on `source` + `external_id`: a repeat returns `200` with `created: false` and updates the first one. |

## Help

Email [support@trutec.ai](mailto:support@trutec.ai) with the endpoint, the time of the call and the response body. Never send your full API key; the first 11 characters (`sk_` plus 8) are enough for us to find it. Live uptime is on our [status page](https://status.trutec.ai).
