# File exports

Every download in the TruTec app is available through one call. `POST /v1/exports` names what you want, and the response is a link that opens or downloads the file for 24 hours, with no API key. Hand the link to a person, or fetch it yourself.

- **The app's own file.** The link runs the same code as the app's Download button, so the PDF, spreadsheet or map is the one your team would get.
- **Made when opened.** Nothing is made or stored up front. Each time the link is opened, the file is made from the data as it is at that moment.
- **The app's rules.** The export acts as the person who created the API key. Their role and your workspace's products decide what they may export, exactly as in the app.
- **Built for assistants.** An AI assistant can't hand you a file, but it can give you a link. The MCP tool `export_file` does this (see [AI assistants](mcp.md)).

For whole tables of data for a warehouse, the [bulk export](bulk-export.md) streams JSON Lines; `workspace_data` below gives a link to the same streams.

## Get a link

```bash
curl -X POST https://trutec.ai/api/v1/exports \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"kind": "proposal", "id": 512}'
```

```json
{
  "data": {
    "kind": "proposal",
    "title": "Proposal PDF",
    "format": "pdf",
    "id": 512,
    "options": {},
    "filename": "Northgate-Plaza-Sealcoat.pdf",
    "content_type": "application/pdf",
    "url": "https://trutec.ai/api/v1/export-files/eyJ2IjoxLCJrIjoicHJvcG9zYWwifQ.ZxY1aQ.k3j9/Northgate-Plaza-Sealcoat.pdf",
    "expires_at": "2026-10-14T15:04:11Z"
  }
}
```

| Field | |
|---|---|
| `kind` | What to export. Required. See [Kinds](#kinds) |
| `format` | One of the kind's formats. Leave out for its default (listed first) |
| `id` | The record, for kinds that export one record. Kinds that cover the workspace take no `id` |
| `options` | Kind-specific settings, such as a date range. Unknown options are refused |
| `firm_id` | Partner keys only: the workspace to act on |

A few kinds let an option stand in for `id`: `job_id` picks the job's newest work order, warranty or certificate, and `estimate_id` picks an estimate's proposal. The response's `id` is the record it picked.

Payroll for a week, as a spreadsheet:

```bash
curl -X POST https://trutec.ai/api/v1/exports \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"kind": "time_entries", "format": "xlsx", "options": {"from": "2026-10-05", "to": "2026-10-11"}}'
```

A takeoff's shapes for Google Earth:

```bash
curl -X POST https://trutec.ai/api/v1/exports \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"kind": "takeoff_geometry", "id": 90311, "format": "kmz"}'
```

`GET /v1/export-kinds` lists every kind with its formats, what `id` names, its options and what it needs, so a script or assistant can discover them.

## Open the link

Open `url` in a browser, or `GET` it. PDFs and images open in the browser tab; spreadsheets, CSV, zip and map files download. The response carries the file name in `Content-Disposition`.

- **Lasts 24 hours.** After that, or if anything below changes, the link returns `404`. Ask for a new link.
- **Checked every time.** Each open checks that the key is still active, that your workspace still has API access, that the key's creator is still in the workspace and still allowed this export, and that the record is still there. Revoking the key stops every link it made.
- **Big files take a moment.** A file still being made returns `503` with `Retry-After` (seconds). In a browser, the page says so and reloads by itself. Takeoff maps are drawn once and reused.
- **`HEAD` is free.** A `HEAD` request returns the content type and file name without making the file, so link previews don't build it.
- **Treat the link like a password.** Anyone with it can open the file until it expires, and files can hold prices, pay and customer details. Share it only with the person who asked.

Each open makes the file again, the same as clicking Download in the app. Payroll exports are logged the same way the app logs them.

## Kinds

**Permission:** Read jobs, except the takeoff kinds, which need Read measurements. *Needs* is what the app requires of your workspace and of the key's creator.

### Estimating & proposals

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `proposal` | pdf | proposal, or `estimate_id` | `estimate_id` | Estimating Pro, pricing access |
| `estimate_workbook` | xlsx | estimate | | Estimating Pro, estimator or admin |
| `estimate_cost_codes` | csv | estimate | | Estimating Pro, estimator or admin, estimate won |
| `catalog` | xlsx | | | Estimating Pro, estimator or admin |
| `work_order` | pdf | work-order sheet, or `job_id` | `job_id` | Estimating Pro |

- `proposal`: the customer proposal, the file from **Download PDF** in the proposal builder.
- `estimate_workbook`: the estimate's lines and prices as Excel (**Share › Download Excel**).
- `estimate_cost_codes`: a won estimate's cost and price by cost code, for QuickBooks.
- `catalog`: the whole catalog as Excel (**Catalog › Download Excel**).
- `work_order`: the printable work order a crew takes to the job (**Work orders › Print / PDF**).

### Operations

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `change_order` | pdf, csv | change order | | Operations; pricing access for csv |
| `daily_report` | pdf | daily report | | Operations |
| `daily_log` | pdf | daily log, or `job_id` + `date` | `job_id`, `date` | Operations |
| `schedule` | pdf | | `week` | Operations |
| `warranty` | pdf | warranty, or `job_id` | `job_id` | Operations |
| `warranty_certificate` | pdf | warranty certificate, or `job_id` | `job_id` | |
| `payroll` | csv | | `from`, `to`, `include_unapproved` | Operations, payroll permission |
| `time_entries` | xlsx, csv | | `from`, `to`, `include_unapproved` | Operations, payroll permission |
| `payroll_hours` | xlsx, csv | | `from`, `to`, `include_unapproved` | Operations, payroll permission |
| `equipment_entries` | xlsx, csv | | `from`, `to` | Operations, Estimating Pro, pricing access |
| `materials_used` | xlsx, csv | | `from`, `to`, `include_unapproved` | Operations |
| `billing_worksheet` | xlsx | | `week` | Operations, Estimating Pro, pricing access |

- `change_order`: the PDF, or the CSV for accounting (header plus lines by cost code).
- `daily_log`: one visit's field log (`id` from `GET /v1/daily-logs`), or every visit on a job that day.
- `schedule`: the printable week of the crew schedule. Money columns show only for people with pricing access.
- `payroll`: closed time entries with an overtime summary per worker-week (**Timesheets › Export payroll**). Approved time only unless `include_unapproved`.
- `time_entries`, `equipment_entries`, `billing_worksheet`: the Accounting reports.
- `payroll_hours`, `materials_used`: the field log exports (hours only; quantities only).
- *Payroll permission* is **Approve and export payroll** in Settings › Team › Roles (owners have it).

### Money

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `job_costing` | xlsx, csv | | `job_id`, `from`, `to` | Operations, Estimating Pro, pricing access |
| `job_cost_out` | xlsx | | `job_id` (required), `from`, `to` | Operations, Estimating Pro, pricing access |
| `month_end_job_cost` | xlsx, csv | | `month` | Operations, Estimating Pro, pricing access |
| `invoice` | pdf | invoice | `with_lien_waiver`, `disputed_amount` | pricing access |

- `job_costing`: budget against actual by category and the cost ledger for one job (`job_id`), or every costed job in the workspace (xlsx takes `from` and `to`).
- `job_cost_out`: one job's cost-out workbook for accounting.
- `month_end_job_cost`: the **Accounting › Month-end** report: every job with revenue or cost in the month, with revenue invoiced, direct cost by type, gross profit and GP %, plus job to date. The same numbers as [`GET /v1/reports/month-end-job-cost`](owner-reports.md#month-end-job-cost). Without `month`, last month (pinned when the link is made).
- `invoice`: the invoice as the customer sees it. `with_lien_waiver` adds the conditional lien waiver as the last page; the waiver must be turned on in **Settings › Payments & Invoicing**.

### Jobs & field

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `site_assessment` | pdf | site assessment, or `job_id` | `job_id` | |
| `job_photos` | zip | | `job_id` (required) | |
| `company_portfolio` | pdf | customer | | |

- `site_assessment`: the branded report: every observation, photo and video, and the aerial with the pins.
- `job_photos`: every photo on the job, full size, in one zip (up to 500).
- `company_portfolio`: a customer's 11x17 paving portfolio: every site and takeoff map, with areas.

### Takeoffs

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `takeoff_map` | pdf, png, jpg | measurement | `style` | Read measurements |
| `takeoff_quantities` | xlsx, csv | measurement | | Read measurements |
| `takeoff_geometry` | kmz, kml, geojson | measurement | | Read measurements |

- `takeoff_map`: the map export: the takeoff drawn on the aerial with its legend and logo. `style: "aerial"` gives the clean imagery instead.
- `takeoff_quantities`: every quantity the takeoff provides with units, the numbers an estimate pulls from it (the same as [`GET /v1/measurements/{id}/items`](takeoffs.md)).
- `takeoff_geometry`: the drawn shapes (areas, lines and counted items) with their type, phase and spec, for Google Earth or GIS. GeoJSON coordinates are longitude, latitude.
- The map and shape kinds need a takeoff drawn on the aerial; plan-sheet takeoffs return `422`.

### Sales & CRM

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `customers` | csv | | `q`, `include_archived` | |
| `contacts` | csv | | `q`, `include_archived` | |
| `sites` | csv | | `q`, `type`, `include_archived` | |
| `jobs` | xlsx, csv | | `status`, `updated_after` | |
| `capital_plan` | xlsx, csv | | `client_account_id`, `location_id` | capital plans turned on |

- `customers`, `contacts`, `sites`: the Companies, Contacts and Locations list exports. `customers` holds up to 5,000 companies.
- `jobs`: status, customer, contact, address, type, value, salesperson and dates for the jobs the key's creator can see (up to 20,000, most recently changed first). Value is left out for people without pricing access.
- `capital_plan`: multi-year plan items by site and year: scope, status, PASER rating and estimated cost.

### Reports and the whole workspace

| kind | Formats | `id` | Options | Needs |
|---|---|---|---|---|
| `management_report` | xlsx, csv, pdf, docx | | `report` (required), `from`, `to` | owner, or a member allowed management reports; pricing access |
| `workspace_data` | jsonl.gz, jsonl | | `dataset` (required), `updated_after` | |

- `management_report`: one of the **Reports › Management** reports: `weekly-summary`, `week-glance`, `receivables`, `cash-forecast`, `top-customers`, `zip-code`, `bid-vs-actual`, `material-usage`, `completed-work`, `bid-schedule`, `lead-pipeline`, `win-loss`, `win-loss-graph`, `brochure-scans`, `work-type`, `market-analysis`, `data-dump`, `estimate-totals`, `item-history`, `material-history`, `resource-utilization`, `sub-utilization`, `vendor-utilization`, `minority`, `weekly-sales`, `kpi`.
- `workspace_data`: one dataset of the [bulk export](bulk-export.md) (`customers`, `properties`, `jobs`, `estimates`, `estimate_lines`, `change_orders`, `invoices`, `job_costs`, `work_orders`, `time_entries`, `allocations`, `events`) as JSON Lines.

## Options

| Option | Type | |
|---|---|---|
| `job_id` | integer | A job. For kinds that otherwise take `id`, picks that job's newest record |
| `estimate_id` | integer | `proposal`: that estimate's proposal |
| `client_account_id`, `location_id` | integer | `capital_plan`: only this customer or site |
| `date` | date | `daily_log` with `job_id`: the day |
| `from`, `to` | date | The date range. Default this week (`management_report`: January 1 to today) |
| `week` | date | Any day in the week. Default this week |
| `month` | string | `month_end_job_cost`: the month, `YYYY-MM`. Default last month |
| `include_unapproved` | boolean | Also time not yet approved for payroll |
| `include_archived` | boolean | Also archived records |
| `with_lien_waiver` | boolean | `invoice`: add the lien waiver page |
| `disputed_amount` | number | `invoice` with the waiver: dollars of disputed claims |
| `style` | `annotated` or `aerial` | `takeoff_map` |
| `report` | string | `management_report`: which report |
| `dataset` | string | `workspace_data`: which dataset |
| `updated_after` | date-time | `jobs`, `workspace_data`: only records changed since |
| `status` | string | `jobs`: one pipeline status |
| `q` | string | `customers`, `contacts`, `sites`: search text |
| `type` | string | `sites`: one building type |

Dates are `YYYY-MM-DD` in your workspace's time zone. Date-times are ISO 8601.

## Finding the id

| kind | Where the id comes from |
|---|---|
| `proposal` | `GET /v1/proposals` ([Proposals](proposals.md)) |
| `estimate_workbook`, `estimate_cost_codes` | `GET /v1/estimates` |
| `work_order` | `GET /v1/work-order-sheets?job_id=` |
| `change_order` | `GET /v1/change-orders` ([Change orders](change-orders.md)) |
| `daily_report`, `daily_log` | `GET /v1/daily-reports`, `GET /v1/daily-logs` ([Daily logs](daily-logs.md)) |
| `warranty` | `GET /v1/warranties?job_id=` |
| `warranty_certificate` | `GET /v1/warranty-certificates?job_id=` |
| `invoice` | `GET /v1/invoices` ([Invoices & payments](invoices-and-payments.md)) |
| `site_assessment` | `GET /v1/site-assessments` ([Site assessments](site-assessments.md)) |
| `company_portfolio`, `capital_plan` | `GET /v1/client-accounts` ([Customers & sites](customers-and-sites.md)) |
| takeoff kinds | `GET /v1/measurements` ([Measurements](measurements.md)) |

`/v1/work-order-sheets`, `/v1/warranties` and `/v1/warranty-certificates` list a job's printable documents (newest first; filter by `job_id` and `status`; paged with `limit` and `offset`). A *work-order sheet* is the document a crew takes to the job; `/v1/work-orders` is the crew schedule.

## Errors

| Status | When |
|---|---|
| `400` | Unknown `kind`, `format`, option or field; `id` or a required option missing |
| `403` | The key lacks Read jobs (or Read measurements), the key's creator is no longer in the workspace, or the app does not allow this export for them. `message` is the app's reason, for example a role without the payroll permission |
| `404` | The record is not in this workspace |
| `409` | The record can't be exported as it is: the estimate isn't won, the lien waiver is off |
| `413` | Too big for one file: a job with more than 500 photos |
| `422` | Nothing to export: a takeoff with no mapped shapes, a job with no photos |

On the link itself, refusals are a page (or plain text for scripts) with the same reason, and anything that makes the link invalid is `404`.

## Not available as links

- **Uploaded files** (plans, contracts, signed documents): `GET /v1/files/{id}` already returns a download link ([Photos & files](photos-and-files.md)).
- **Exports built in the takeoff editor from what's on screen**: image templates, the export wizard's custom layouts, the legend export, resident notices and earthwork reports. `takeoff_map`, `takeoff_quantities` and `takeoff_geometry` cover the saved takeoff.
- **Estimating Expert** documents, RoofTrace and landscape reports, and the older proposal builder from before Estimating Pro.
