Thermal
Reference

API changelog

This is the record of changes to the Thermal Platform API. Check it before updating an integration. Breaking entries include the migration path.

2026-10-14 — Governed HTTP write limits

OpenAPI SHA-256: de0d43ecd1c0c1fbf4766ee03134d392f28379566db7d914d61b70399bf99e2a

Breaking. Governed POST and DELETE requests, including /batch, require Content-Type: application/json; missing or other media types return 415 unsupported_media_type. Send this header even for a bodyless DELETE.

Batch admission now reserves one unit per item from the organization, principal and write-class rate limits before any item runs. A refused batch charges no units; items that never reach the write engine return their reserved units. The current write-class limit is 60 units per minute, so split larger batches within that budget and honor the 429 retry headers.

Governance reasons supplied in the body or X-Thermal-Reason are bounded to 1000 characters, including an explicit [truncated] marker when shortened. Per-item batch reasons still reject values over 1000 characters. Hand-written routes now replace oversized or invalid X-Request-ID values with a generated ID in both the response header and error body; valid IDs keep their value.

2026-10-14 — Dispatch writes restricted to staff sessions

OpenAPI SHA-256: 3168b38589f97789974912e5d06fe360751c299d2db6b39fd22e496cd6409ccd

Breaking. work_order.assign and dispatch.auto_assign are staff-session operations. Organization API keys and OAuth clients can no longer invoke them through the governed Platform API or MCP mounts. Use a signed-in staff session for these dispatch changes; the two operations are no longer advertised by MCP.

2026-10-12 — Who am I

OpenAPI SHA-256: 3168b38589f97789974912e5d06fe360751c299d2db6b39fd22e496cd6409ccd

Additive. principal.whoami (GET /whoami) returns the principal the server resolved for the presented credential: a user session, an OAuth client token or an org API key. The response names its type, id, delegation class, role ceiling and scopes, the organization it is acting in, and every organization it may act in. The delegation class always comes from the stored credential, never from the request. Send the optional thermal-organization header to select an organization; naming one the credential cannot act in returns 403 wrong_org. A credential that may act in more than one organization and names none returns 409 ambiguous_organization, listing those organizations and no principal. The contract now contains 289 operations.

2026-10-12 — PO numbers at the supply counter

OpenAPI SHA-256: 1ce650e5aaea41509f7ec3d1f14fc10430eabb1bfb3b7bf6ab1d2b8cba8afe9c

Additive. purchase_order.issue_counter (POST /field/work-orders/{id}/counter-po) returns a company-wide PO number for a technician with a live appointment on that job. Supply either vendorId or vendorName, an optional estimatedAmountCents, and a stable client idempotencyKey. The client key also deduplicates a request made through the technician web app. The normal Idempotency-Key header remains required for Platform API replay.

An estimate at or above the office's approval threshold returns pending_approval, with its number visible. An omitted estimate returns ordered. The field_purchase.submit write accepts an optional purchaseOrderId linking the same technician's counter PO for the same job. Office receipt approval marks an ordered PO received, even when the actual amount exceeds the original estimate. Pending, canceled, and office-rejected POs stay unchanged, with the reason recorded in their activity. The contract now contains 288 operations.

2026-10-12 — Internal viewer flag on the technician day

OpenAPI SHA-256: 569f637c0e35e0a163441884e762f690dd90f61976447a7714db25d0157827a4

Additive. technician.day (GET /field/day) now includes an optional viewerIsInternal boolean, classified on the server from the signed-in login's verified sign-in addresses. Mobile analytics uses it to mark internal and test logins. The field is omitted when the classification is unavailable, and no address is returned. Offline replica reads do not supply this field. No operations were added. The contract still contains 285 operations.

2026-10-12 — Staff messages from the technician app

OpenAPI SHA-256: 19940f0d9379c9720905e0ba495d233ded6461bf72a1b4d89abc6e0bfd848762

Additive. field_work_order.messages (GET /field/work-orders/{id}/messages) returns a page of staff messages, total and unread counts, and an earlier-page cursor. Technicians can read only jobs with their own non-canceled appointment; staff access stays within their organization and branch scope.

work_order.post_message (POST /work-orders/{id}/messages) sends a staff message. work_order.mark_messages_read (POST /work-orders/{id}/messages/read) advances the signed-in person's read boundary. Both writes require a staff session and an idempotency key. Replaying a send stores one message; an older receipt cannot move the read boundary back. Customers cannot access this thread. The contract now contains 285 operations.

2026-10-11 — Site coordinates and automatic arrival setting

OpenAPI SHA-256: 868e0c3a5a8bf344fcdd0b7a7f14a5500042107ca18b61d56a9939edc85f109f

Additive. field_work_order.get now returns the service site's geoLat and geoLng (nullable numbers) to a caller allowed to read that visit, and technician.profile returns fieldApp.autoArrive, the organization's automatic-arrival setting. No operations were added. The contract still contains 278 operations. See the generated API contract.

2026-10-11 — Technician visit arrival

OpenAPI SHA-256: 1ea219cc2855b6bd2a6edff5b0e84108e168c2f809591abb26ac6166a32fc23a

Additive. Assigned technicians can mark their current visit arrived through appointment.mark_arrived (POST /appointments/{id}/arrive), with appointment.mark_arrived.preview and appointment.mark_arrived.approve for governed review. The write requires an idempotency key. The contract now contains 278 operations. See the generated API contract.

2026-10-10 — Operational review prompts and read projections

OpenAPI SHA-256: 23170e3cca2e8a0cb42540203efa00db5951f6b7df5d63fe12a01c013804d1ae

Additive. Ten operational review prompts are available through MCP. Their operations.* reads return small, permission-scoped pages of records and the organization's review thresholds. Continue through every nextCursor before claiming a complete review. Suggestions identify existing actions for a person to review; the prompts do not execute them. The contract now contains 275 operations. See operational skills.

2026-10-10 — Technician job holds and resume

OpenAPI SHA-256: 0a6b907db455c35eab6e2151491aa1daad4909509517821238175ebbab0b6cbe

Additive. Assigned technicians can record a job hold with a reason and optional note through work_order.hold (POST /work-orders/{id}/hold), and clear the reason and note through work_order.resume (POST /work-orders/{id}/resume). Both writes require a row version and an idempotency key. The contract now contains 265 operations. See the generated API contract.

2026-10-09 — Technician location while on the way

OpenAPI SHA-256: 56547fe63aee52c4ebbbdb6acbc3f37451ec94c6d1ff48545d8ccdf5ec8ccee8

Additive. field_location.report (POST /field/location) accepts location pings for the authenticated technician's own en-route visit. The contract now contains 259 operations.

2026-10-09 — Organization identity on the technician day

OpenAPI SHA-256: 94726fade2f3e42105f02193a65ce5e20a69ea58dfd2fedd8c96f332ca59e387

Additive. technician.day (GET /field/day) now includes the required organizationId UUID resolved from the authenticated server context. It is the internal organization identifier, rather than the Clerk organization id. Mobile analytics uses it to associate a live day read with its organization; offline replica reads do not supply this field.

2026-10-08 — Parts stock for a work order

OpenAPI SHA-256: c5738e84f6dcd423506619ae630ec000ab187119d8d9e020db8d1c8a61662f3d

Additive. field_work_order.parts_stock (GET /field/work-orders/{id}/parts-stock) returns one stock entry per pricebook item: the caller's truck quantity, other trucks in the job's branch with holder names and quantities, and the branch warehouse quantity. Technicians need a non-canceled appointment on the job; office roles may read within their organization and branch scope. Parts without stock are absent, and a job without a branch returns an empty list. No query parameters are accepted. The contract now contains 258 operations.

2026-10-08 — Leave-by times from stored route legs

OpenAPI SHA-256: 6ab6c6f0194f1d0e39b2d341c9f843d6d593112489ef5bc15ab733e970c5b9ec

Additive. Each item of technician.day (GET /field/day) and each stop of technician.route_today (GET /route) may now include driveMinutes, the stored route plan's drive time from the previous stop, and leaveBy, the promised window start minus that drive time. Format leaveBy in the response's timeZone. Both are omitted when no stored leg reaches the stop from the stop before it, including the day's first stop; neither is estimated from distance (#5815).

2026-10-06 — Status changes can carry a short note

OpenAPI SHA-256: c059303df28cdec32a1211d33cbcacbc99b13ad1e159b9b1ef5ffad6be76505c

Additive. work_order.update_status (POST /work-orders/{id}/status) and its .preview accept an optional note of up to 280 characters. It is trimmed, an empty note is ignored, and a longer one is a 400 validation error. The note is stored with the status change and is part of what a preview approves, so an apply that presents a different note than its proposal is refused. The status preview's lastStatusChange now includes an optional note, null when the change carried none. The 409 conflict body is unchanged (#6283).

2026-10-05 — Status preview says who last changed the status

OpenAPI SHA-256: aba2fdf9d89a47da8145b53b1a8518b68014115f72ef65c94b970a3355fff413

Additive. The work_order.update_status.preview response (POST /work-orders/{id}/status/preview) now includes an optional lastStatusChange: updatedBy (userId, displayName, roleLabel; null when the change was not made by a staff user of your organization), updatedAt and previousStatus. It is null when no recorded status change matches the work order's current status. The 409 conflict body is unchanged (#5351).

2026-10-04 — Field deposits before job completion

OpenAPI SHA-256: c9f22615d6422238c6789ef4d7addb78ed7c6758d9852497571df0d5d17c26e4

Additive. takeFieldDeposit (POST /work-orders/{id}/deposits) is added, with its .preview and .approve companions: before a job is completed, the technician on an en_route, arrived or in_progress visit takes a deposit by cash, cheque, card or pay link, up to what is left of the job's current total. It requires an Idempotency-Key. field_work_order.balance (GET /field/work-orders/{id}/balance) now also returns mode (deposit, balance or none, the collection the server accepts right now), depositVisitId and depositRemainingCents (both set only in deposit mode). The contract now contains 257 operations (#5199).

2026-10-04 — Technician voice session lifecycle

OpenAPI SHA-256: 2281f60e900e3b53d70236f7743d62e95a5d9464f27d62685851b5d07cb88030

Additive. voice.sessions.open (POST /voice/sessions) opens a technician's session and returns a provider credential valid for at most 60 seconds. voice.sessions.end (PATCH /voice/sessions/{id}/end) ends the caller's own session and records its usage once. Organization, user and branch come from the signed-in principal. Voice Off refuses both operations. technician.profile now includes the voiceAssist level. The contract now contains 254 operations.

2026-10-04 — Technician day and route include the organization's time zone

OpenAPI SHA-256: fea84222eda6e006fec27d24e38bc98bb0476bb7e5851eedadc49b5644c15887

technician.day (GET /field/day) and technician.route_today (GET /route) now return a required timeZone: the organization's IANA time zone, the same zone the server uses to decide which visits fall on today. Render each scheduled time in this zone rather than the device's (#5405).

2026-10-03 — Field work-order balance identifies its invoice

OpenAPI SHA-256: fbccb270ef8b0c02545fc4dc25ce736231c62a74c2ba8c59b93aea2d3989a8da

Additive. field_work_order.balance (GET /field/work-orders/{id}/balance) now includes invoiceId. It identifies the sole live invoice that bills only this work order. It is null when no such invoice exists, when multiple live invoices exist, or when an invoice bills multiple work orders.

2026-10-03 — Optional Thermal-Client header for client version checks

OpenAPI SHA-256: dc7bfc4e7b85aa3dfe5910808b58121b4f6f92d2ee0681808bb4972c0a443fd2

Additive. Every operation now accepts an optional Thermal-Client request header, <platform>/<semver>[+<build>] (for example mobile/1.2.3+45), that identifies a first-party client build. A known platform below its minimum supported version is refused with 400 client_upgrade_required, described by the new ClientUpgradeRequired schema. Requests without the header, with an unparseable value, or from an unknown platform are never refused, so existing integrations are unaffected (#5364).

2026-10-02 — Receive a job's purchase order onto the technician's van

OpenAPI SHA-256: c4b734ae6c475de3c82baf814b8bd4a39e44077179d35b3ad69ac2ebbe8a55cb

Additive. purchase_order.receive_to_van (POST /van-stock/purchase-orders/{id}/receive) is added with its .preview and .approve companions: a technician on the job receives whole-number quantities of a purchase order's lines into their own van. The purchase order must be ordered or partly received, and a quantity above what is still open is refused rather than trimmed. field_work_order.purchase_orders (GET /field/work-orders/{id}/purchase-orders) lists the job's purchase orders that can still be received, with each line's open quantity. The contract now contains 252 operations (#5686).

2026-10-02 — Technician profile clock descriptions cover earlier days

OpenAPI SHA-256: af217228197da2319901520399f151213a1627b5443f9c5c092e6860bada8e2f

technician.profile (GET /profile): description-only change, no shape change. clock.openEntry now says it can be an entry left open from an earlier day, which is clocked out with an occurredAt end time, not only one started today. The closed-entry item description now says the list can include an earlier day's draft that was never submitted (#5204).

2026-10-02 — Device push-token registration

OpenAPI SHA-256: c77e6b5155253bccc937d3cf6505541fcba9f65efa34db833209973a30e18432

registerPushToken (POST /me/push-tokens) and revokePushToken (POST /me/push-tokens/revoke) are added, each with its .preview and .approve companions. A signed-in user registers or revokes the push token for one of their own devices. The contract now contains 248 operations (#5009).

2026-10-01 — Technician day stops include customer and address

OpenAPI SHA-256: 94b9e058f95bef665021d34bd35b10d8b3d2cf9803255750f0b97aca446be832

Additive. Each item returned by technician.day (GET /field/day) now includes customerName and siteAddress, the service site's customer and its address on one line. Both are null when the site or its customer has been deleted. work_order.list is unchanged.

2026-10-01 — Field work-order detail adds status history

OpenAPI SHA-256: 5eb4e5e26f28bd768c19d58a043b2cd3c5786e0b584710fb991bb6ac3bc3da93

field_work_order.get: additive optional statusHistory — read-only status transitions for the work order and its current visit, newest first, at most 50 (#5353).

2026-10-01 — Legacy work-order detail endpoint deprecated

OpenAPI SHA-256: 057f3fec9083a408a5a08b40f401b7554e7dbfc29b8e81d27ae4e8a26eb92e06

legacy.work_order.get now advertises its retirement. New integrations should use work_order.get; the legacy endpoint remains available during its deprecation window.

2026-10-01 — Initial /api/v1 contract

OpenAPI SHA-256: 9fcfea4ca8d2a9d7cf17e09ddf575e1c29ecf94031ac9a0214afb5d08da7b4ff

The initial public contract contains 242 operations. The live reference is at /developers; its OpenAPI document is served at /developers/openapi.json.

On this page

2026-10-14 — Governed HTTP write limits2026-10-14 — Dispatch writes restricted to staff sessions2026-10-12 — Who am I2026-10-12 — PO numbers at the supply counter2026-10-12 — Internal viewer flag on the technician day2026-10-12 — Staff messages from the technician app2026-10-11 — Site coordinates and automatic arrival setting2026-10-11 — Technician visit arrival2026-10-10 — Operational review prompts and read projections2026-10-10 — Technician job holds and resume2026-10-09 — Technician location while on the way2026-10-09 — Organization identity on the technician day2026-10-08 — Parts stock for a work order2026-10-08 — Leave-by times from stored route legs2026-10-06 — Status changes can carry a short note2026-10-05 — Status preview says who last changed the status2026-10-04 — Field deposits before job completion2026-10-04 — Technician voice session lifecycle2026-10-04 — Technician day and route include the organization's time zone2026-10-03 — Field work-order balance identifies its invoice2026-10-03 — Optional Thermal-Client header for client version checks2026-10-02 — Receive a job's purchase order onto the technician's van2026-10-02 — Technician profile clock descriptions cover earlier days2026-10-02 — Device push-token registration2026-10-01 — Technician day stops include customer and address2026-10-01 — Field work-order detail adds status history2026-10-01 — Legacy work-order detail endpoint deprecated2026-10-01 — Initial /api/v1 contract