Bulk-load Orders, Projects, Tickets, and Files into Tread with async ingest endpoints, then check on results after the fact.
The Importer APIs bulk-load data into Tread. Send an array of Orders, Projects, Tickets, or Files in one request instead of calling the REST API record by record. Each endpoint saves your payload and processes it in the background.Use the Importer APIs for bulk loads — seeding historical data, a nightly sync from your ERP or accounting system, or a high-volume data feed. Use the REST API endpoints elsewhere in this reference when you’re creating or updating a single record.
This API is in Beta. Behavior may change. Contact developers@tread.io with questions or feedback.
Every Importer endpoint lives under the same host as the rest of the API, with an /ingest prefix.
https://api.tread-horizon.com/ingest
Authentication uses the same bearer token as the rest of the API. See Authentication for how to get one.
Authorization: Bearer <access_token>
Content-Type depends on the endpoint. Orders, Projects, and Tickets take JSON:
Content-Type: application/json
Files takes a multipart upload instead — see Files.Every JSON payload can include an optional metadata object for your own tracking, like a batch ID or source system name. Tread stores it but doesn’t act on it.
Orders, Projects, and Tickets process asynchronously. A 202 Accepted response means Tread saved your payload and queued it for processing. It does not mean the record was created or updated.
202 Accepted is not success. Always check the imported record’s state after you send a payload.
Two ways to check on a payload after you send it:
Reports > API Integrations in the Tread app. Shows Imported For, Import Received At, State, Last Error, and the raw payload you sent. Defaults to the last 7 days, most recent first.
Error webhooks, if you’ve configured one. Tread pushes an event when a payload fails to process. See Webhooks.
Each imported record ends up in one of these states:
State
Meaning
created
Received. Not yet processed.
processed
Succeeded.
failed
Failed. Retries pending.
exhausted
Failed. All retries used up.
skipped
A business rule blocked it — for example, a duplicate payload.
skipped_update
An update was skipped because a user edited that field in the Tread app.
If a record is failed or exhausted, read its last_error field for the reason, fix the source data, and resend. Tread deduplicates automatically, so resending the same payload is safe.The Files endpoint is the one exception — it processes synchronously. See Files.
Matching is by external_id, scoped to your company. A match updates the order’s root fields — name, quantities, rates, notes, and so on.
customer, material, equipment_type, and sites are nested records. Tread creates them if they don’t exist but doesn’t update them on a match — see external_id matching.
foremen and vendors are arrays. Every payload replaces the whole array — it doesn’t merge with what’s already there. This is true even if you omit the field: omitting foremen does not preserve the existing foremen. Send the complete set every time.
If an order is canceled and you later send a payload with the same external_id in a non-canceled state, Tread renames the canceled order’s external_id to X-{N} and creates a new order. This is automatic — no cleanup needed on your end.
effective_at must be strictly before expires_at. If your source data has them equal — common for one-day projects — omit expires_at entirely. Don’t set it to end of day; just leave it off.
Matching is by external_id. A match updates root fields — name, dates, notes, rates.
Phase sync is destructive. Every payload replaces the full phase list. A phase left out of the payload is deleted. Always send the complete set, not just what changed.
Materials sync the same way. A material left out of the payload is removed from the project.
Sites need an address. Every site needs full_address or lat/lon. If your source data only has a site name, set full_address to that name — Tread geocodes it automatically. Without one of these, the site fails validation and is silently dropped: the project still reports success, but that pickup or drop-off is missing and no error surfaces.
foremen replace on update, the same as Orders.
If a project’s name collides with a different project’s name, Tread appends -{external_id} to keep both.
Tread links a ticket to an order two ways: tread_order_id (direct, preferred) or dispatch_number plus a matching job start date (fallback). Send tread_order_id when you have it.
unit_of_measure normalization is case-insensitive but exact values are safer. "tons" becomes "Ton", but a string the system doesn’t recognize can silently fall back to the default (Ton) instead of erroring. Use the exact enum value.
site is a single object on Tickets — unlike Orders and Projects, where sites is an array.
POST /ingest/files attaches a file to an existing (or soon-to-exist) Order. It uses multipart/form-data, not JSON — every other Importer endpoint uses JSON.
Files process synchronously — the one exception among the Importer APIs.
201 Created — the Order already exists. Tread attaches the file immediately.
202 Accepted — the Order doesn’t exist yet. Tread stores the file and attaches it automatically once a matching Order arrives — files and orders can arrive in either order. No extra call needed.
422 — the file failed validation (wrong type or over 10 MB).
Every Importer endpoint uses external_id — your system’s identifier — to decide whether to create a new record or update an existing one. It’s scoped per company and case-sensitive.Root records (Order, Project, Ticket) work as you’d expect: found by external_id → update; not found → create.Nested records (customer, material, equipment type, sites, foremen) work differently. Tread creates them if they don’t exist, but generally doesn’t update their fields once matched. Sending a different name for a customer that already exists doesn’t rename it.
Nested record
Matched by
Customer
Name, then external_id
Material
Name, then external_id
Site
Name and external_id together, then either alone
Equipment Type
Name or external_id
Foreman
Email, then phone, then external_id
To rename a customer, material, or other master-data record, use the REST API elsewhere in this reference — not the Importer APIs.Arrays follow one of three patterns on update:
Array
Behavior
foremen, vendors, collaborators, salespeople
Replace — the whole array is swapped
phases, materials (Projects only)
Sync — items missing from the payload are deleted
sites
Upsert — matched by waypoint type; the record’s ID is preserved
202 doesn’t mean it worked. Check the imported record’s state before you treat a payload as done.
Nested records don’t update on match. Renaming a customer or material in your payload has no effect once that record already exists. Use the REST API to rename it.
Sites without an address are dropped, silently. No full_address or lat/lon on a site means Tread drops it — and the Project or Order still reports success.
Invalid phone numbers are dropped, silently. A phone that doesn’t normalize to E.164 is stripped from the contact. No error, no last_error entry.
Duplicate payloads are skipped, not reprocessed. Sending the exact same payload twice marks the second one skipped. This is expected, not a bug.
Phase sync deletes what you leave out. Every Project payload replaces the entire phase list, not just the phases you’re changing.
Foremen and vendors replace, they don’t merge. Every payload with a foremen or vendors array swaps the whole thing, even if you omit the field.
Unit of measure is picky."tons" normalizes to "Ton", but a string the system doesn’t recognize silently falls back to the default (Ton) instead of erroring. Use exact enum values.
Need to create, update, or delete one record at a time instead of bulk-loading? Use the REST API endpoints elsewhere in this reference. They’re synchronous — a 201 or 200 means it’s done — and they support full field updates on nested records, including renaming a customer or material. The Importer APIs are for bulk loads only.