Bulk data export
Stream whole datasets (customers, jobs, estimates, invoices, costs, labor, events) as JSON Lines, full or incremental, whenever you want.
View as MarkdownThe bulk export streams whole tables of your workspace's data as newline-delimited JSON (JSON Lines). Use it to load a data warehouse, to back up your data, or to take your data with you.
- On demand. Nothing runs on a schedule and no file is stored. Each request reads your data at that moment and streams it to you. You can pull as often as you like.
- Full or incremental. Leave out
updated_afterfor every row. Pass it to get only rows that changed. - One format. JSON Lines, one JSON object per line, UTF-8. Add
.gzto the URL for gzip. CSV and Parquet are not offered. Most warehouse loaders read JSON Lines directly. - Your workspace only. Each request returns rows for the workspace that owns the key. Partner keys add
?firm_id=(see Partner API). - A link instead of a key. To hand a dataset to a person, or get one through an AI assistant, ask for a 24-hour link with
POST /v1/exportsandkind: workspace_data(see File exports).
Datasets#
| Dataset | One row per | Changed-since fields |
|---|---|---|
customers |
Customer (billing entity) | updated_at |
properties |
Property / jobsite, with coordinates | updated_at |
jobs |
Job | updated_at |
estimates |
Estimate header with totals | updated_at |
estimate_lines |
Estimate line item. All lines of an estimate are re-sent when the estimate changes | updated_at (of the estimate) |
change_orders |
Change order with value deltas | updated_at |
invoices |
Invoice with payment state | updated_at |
job_costs |
Job cost entry | created_at, reviewed_at, reconciled_at |
work_orders |
Crew work order (schedule and completion) | updated_at |
time_entries |
Crew clock-in/out: hours, category, wage rate | clock_in, clock_out, approved_at, voided_at |
allocations |
Work order, with its crew, people and equipment | updated_at (of the work order) |
events |
Event from the event log | occurred_at |
With updated_after, a row is included when any of its changed-since fields is later than the value you pass.
Row shapes:
customers: the customer object from Customers & sites, plusarchived_at.properties:id,name,address,external_ref,client_account_id,latitude,longitude,property_type,pci,archived_at,created_at,updated_at.jobs: the job object from Jobs, includingcustom_fields.estimates: the estimate summary from Estimates & proposals (totals,current_version,awarded_at,archived_at, ...), without line items.estimate_lines: the line item object from the estimate detail, plusestimate_id,job_id,area_id,area_name,estimate_updated_at.change_orders:id,job_id,number,title,kind,status,source,stage,amount_delta,tax_delta,cost_delta,days_delta,ep_estimate_id,decision_comment,sent_at,signed_at,created_at,updated_at.invoices,job_costs,time_entries,allocations: the same objects asGET /v1/jobs/{id}/invoices,/costs,/laborand/allocationin Job costing.work_orders:id,job_id,title,status,phase,crew_id,display_qty,display_unit,completed_qty,completed_unit,estimated_days,scheduled_date,dispatched_at,completed_at,created_at,updated_at.events:id,event_id,event_type,occurred_at,entity_type,entity_id,job_id,data. Note thatentity_typeandentity_idare flat here, unlike the nestedentityobject inGET /v1/events.
List datasets#
GET /v1/exports
Permission: Read jobs
curl https://trutec.ai/api/v1/exports \
-H "X-API-Key: sk_your_api_key_here"
{
"data": [
{
"dataset": "customers",
"description": "Customers (billing entities)",
"changed_since_fields": ["updated_at"],
"url": "/api/v1/exports/customers.jsonl.gz"
},
{
"dataset": "job_costs",
"description": "Job cost entries by category",
"changed_since_fields": ["created_at", "reviewed_at", "reconciled_at"],
"url": "/api/v1/exports/job_costs.jsonl.gz"
}
]
}
Download a dataset#
GET /v1/exports/{dataset}.jsonl or GET /v1/exports/{dataset}.jsonl.gz
Permission: Read jobs
| Name | Type | Required | Description |
|---|---|---|---|
dataset |
path | Yes | A dataset name from the table above |
updated_after |
ISO 8601 | No | Only rows changed after this time. Use the watermark from your last pull |
# Full export, gzipped
curl -o jobs.jsonl.gz https://trutec.ai/api/v1/exports/jobs.jsonl.gz \
-H "X-API-Key: sk_your_api_key_here" -D headers.txt
# Incremental: only rows changed since the last watermark
curl "https://trutec.ai/api/v1/exports/jobs.jsonl?updated_after=2026-10-11T06:00:00Z" \
-H "X-API-Key: sk_your_api_key_here"
Response body (one object per line, shortened here):
{"id":4812,"name":"Northgate Plaza","company":"Northgate Property Group","address":"1200 Commerce Dr, Austin, TX","status":"won","status_label":"Won","estimated_value":48250.0,"client_account_id":915,"location_id":2290,"external_ref":"ERP-JOB-20931","custom_fields":{},"created_at":"2026-10-01T14:02:09.331201","updated_at":"2026-10-12T13:48:41.102938"}
{"id":4813,"name":"Riverside Medical Sealcoat","company":"Riverside Health","address":"88 Lake Rd, Austin, TX","status":"lead","status_label":"Lead","estimated_value":null,"client_account_id":917,"location_id":2295,"external_ref":null,"custom_fields":{},"created_at":"2026-10-12T09:15:00.000000","updated_at":"2026-10-12T09:15:00.000000"}
Response headers:
| Header | Meaning |
|---|---|
Content-Type |
application/x-ndjson, or application/gzip for .gz |
X-TruTec-Export-Dataset |
The dataset name |
X-TruTec-Export-Watermark |
Save this. Send it as updated_after on your next pull |
Content-Disposition |
attachment; filename="<dataset>.jsonl[.gz]" |
Rows are streamed in id order. The response has no total count and no pagination. Read until the stream ends. An empty dataset returns 200 with an empty body.
Errors:
404Unknown dataset, with adatasetslist of valid names404Use /api/v1/exports/<dataset>.jsonl or .jsonl.gz(any other extension, such as.csv)400updated_after must be an ISO 8601 timestamp403API key lacks job read permission
Keeping a copy in sync#
- Run a full export of every dataset once.
- Store each dataset's
X-TruTec-Export-Watermark. - On a schedule (for example nightly), pull each dataset with
updated_after=<stored watermark>and upsert rows byid(work_order_idforallocations;estimate_idplus lineidforestimate_lines). Then store the new watermark. - Run a full export now and then (for example weekly) to catch deletes.
The watermark is set one minute before the export started. A row saved while an export is running is sent again on the next pull, so upserts are safe and nothing is missed.
Warning
Incremental pulls cannot show hard deletes. A deleted row simply stops appearing. Archived customers, properties and estimates carry archived_at. Compare a periodic full export with your copy to find rows that were removed.
Other limits of incremental pulls:
job_costshas noupdated_at. An edit to a cost entry after it was created shows up incrementally only when it is reviewed or reconciled. Full exports always have the current values.estimate_linesre-sends every line of an estimate when the estimate'supdated_atmoves. Replace all lines for thatestimate_idrather than upserting single lines.
What the export does not include#
The export covers the business records above. These are not in it:
- Measurements and takeoffs (geometry, areas, counts, quantities). Use
GET /v1/measurementsandGET /v1/measurements/{id}(Measurements). - Photos, videos, documents and other files. Use
GET /v1/jobs/{id}/photosfor job photos. Other files are not available by API. - Proposal documents, e-signatures and proposal messages. The
proposal_*webhooks carry status only. - Issued estimate versions and the estimate edit log. The export has the live estimate. Use
GET /v1/estimates/{id}/versions,GET /v1/estimates/{id}?version=NandGET /v1/estimates/{id}/revisions. - Estimate lines that are hidden from bid totals. Suppressed and inactive services and inactive areas are left out, the same as on the customer proposal.
- Invoice line items and invoice bill-to and remit-to text. The
invoicesdataset has amounts, tax, discount, dates, status and QuickBooks sync state. - Notes, activity timeline, tasks, emails and text messages.
- Contacts (people) as their own records. Customer billing contact fields and job contact fields are included.
- Your settings and catalog: services and pricing, rate books, pipeline stages, custom field definitions, crews and equipment lists (crews, people and equipment appear only as assigned on work orders in
allocations), users and permissions. - Assessments and condition ratings, and webhook delivery history.
Access and your data#
- Your data belongs to you. You can export it at any time while your workspace has API access.
- API access needs an active plan that includes it (Gold or higher). If the subscription is canceled, past due or the trial has ended, API calls, including exports, are refused. Run a full export before you cancel.
- Exports do not count toward your key's usage. See Limits & billing.
- Exports are generated live and never written to storage, so there is no export file to expire or leak.