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.