Added Nesting Nestable Jobs List — POST /nesting/nestable-jobs/list, a paged list of nestable groups: a material shape paired with the operation it is cut on, with the job operations still waiting to be nested and the work-order operations already created. Filter on needNesting, existingNests, materialForms, operationIds, materialShapeIds, earliestStartOnOrBefore and search. earliestStartOnOrBefore is a day-inclusive calendar date (YYYY-MM-DD; a valid time or offset is ignored) and drops groups with no dated job operation, so existing-nest-only groups never survive it. New endpoint — no existing behavior changes.
Added Work Order Operation Nesting Setup — GET /work-orders/{workOrderId}/operations/{workOrderOperationId}/nesting-setup, returning the nest planned onto one work-order operation: its parts, the sheets it cuts them from with completedCount against count, and the parts it could not place. An operation with no nest answers 200 with isNested false, machineTimeSeconds0 and empty lists. Returns 404 if the work order does not exist or has been deleted, or has no operation with that id. New endpoint — no existing behavior changes.
Changed qtyCompletedThisRun on Reporting Job Activity By Operator List to net a day's negative entries instead of dropping them, so a day can read lower than it reported, negative, or zero; unitsPerHour moves with it. The run close-out now stands in only for an operation with no non-zero entries. Deleted job-log entries no longer count here or in qtyInventoriedThisRun. A day whose entries net to zero, previously omitted, now returns a row, so totals and paging can change.
Changed item writes to require an inventory unit of measure. An item has always been meant to carry one, but several paths let an item reach the database without it, and downstream inventory, costing, and purchasing then had no unit to work in. Item Update, Item Patch, the item tag endpoints (POST and DELETE /items/{itemId}/tags/{tagId}, PUT /items/{itemId}/tags), and Item Add Revision now answer 400 when the item they target has no inventory unit of measure, rather than writing or cloning an item that cannot be stocked. Item Create is unchanged: unitOfMeasureName was already required there. Set the unit on the item first, through Item Update or in the app, and the call succeeds. Only items already stored without a unit are affected, so an integration whose items all carry one sees no change.
Changed how Create Attachment classifies an uploaded PDF. A 3D PDF — a .pdf whose bytes carry a PRC 3D-annotation stream — is now recognized as a CAD file, so it is processed for the in-house 3D viewer and receives a rendered thumbnail, the same as any other CAD upload. A plain 2D PDF is unchanged: it stays on the document path with its usual thumbnail. The distinction is drawn from the file's contents, not its extension or the request — nothing in the request selects it, and no other file type is affected. A PDF that is not a 3D PDF, and every non-PDF upload, behaves exactly as before.
Added unitOfMeasureName to Item Update and Item Patch, so a stocking unit picked wrongly at create time can be corrected without a trip into the app. Omitting the field — or sending null — keeps the item's current unit, the same way number and externalReferences behave; every other field on the update remains an unconditional overwrite. The unit must belong to the item's existing unit type: a Pieces item accepts Piece, Set or Case, and anything else is a 400 naming the item's unit type and the units it will accept. The unit type itself stays create-only, here and in the app — an item created under the wrong unit type has to be recreated. A change is only accepted while nothing still depends on the current unit, because nothing is converted when it changes: on-hand quantities are stored as bare numbers, a sales unit of measure conversion is a ratio against the current unit, and a price break that names an amount — item, vendor, customer, or customer-tier — is a price against a unit of the item's. A break holding only a margin or a markup is a percentage, so it survives the change and does not block it; creating an item seeds exactly one of those from the shop's default margin, which is why a freshly created item can still be corrected. So a change is rejected with a 400 when the item is in use by a quote, sales order, purchase order, job, invoice or another item's routing; when it holds inventory on hand; when the request sends sales unit of measure conversions alongside the change; or when any of its price breaks carries a price. Each 400 names which of those blocked it. Past inventory transactions are not checked and are not converted: an item whose on-hand has returned to zero may change its unit — the same as correcting it in the app — and its earlier transaction rows are then read in the new unit. Clear the blocker in one request, then change the unit in a second — each condition is read as the item stands before the update, so doing both at once is still rejected. The one exception is salesUnitOfMeasureConversions: because an update replaces them wholesale, a request that changes the unit and sends none is accepted and leaves none behind, while a request that changes the unit while conversions are present — sent on the update, or carried forward from the item by a patch — is rejected. An item with no unit type recorded is also rejected, and its unit cannot be corrected through this endpoint at all. Sending the unit the item already has is not treated as a change, so a read-modify-write integration that echoes the field back keeps working on items in any of those states.
Changed nextCalibrationDateUtc on Tool List and Tool Get: a tool that is inactive with reason retired or lost now returns null — it is permanently out of service, so nothing is ever due against it, the same as a tool with calibrationFrequencynone. Other inactive reasons still return the computed date. lastCalibrationDateUtc and the tool's calibration records are unchanged, and clearing the reason restores the date. Consumers deriving due lists from this field will see retired/lost tools drop out; request and response shapes are unchanged.
Added a salesOrderNumbers filter to Reporting Sales Order Lines List, matching lines by the sales-order number shown in the product. A consumer tracking a known set of orders can ask for exactly those orders in one call instead of pulling a broad date or status slice and discarding most of it — which also narrows the query itself, not just the response. Accepts up to 500 numbers, matched exactly; duplicates are ignored, and sending an empty list (or omitting the field) returns unfiltered results as before. A longer list is rejected rather than trimmed, so a request can never quietly match fewer orders than it asked for. Combines with every existing filter. Addition-only — existing requests are unaffected.
Corrected the published response for Sales Order PDF — GET /sales-orders/{salesOrderId}/pdf now declares its 200 as application/pdf binary content in the OpenAPI schema, matching Purchase Order PDF. Generated clients that previously typed the response as empty will now type it as a binary download. The bytes on the wire are unchanged.
Added Sales Order PDF — GET /sales-orders/{salesOrderId}/pdf, returning application/pdf as SalesOrder_{number}.pdf. The PDF reflects the order's current state and the tenant's PDFs & Emails settings at the time of the request; it is not a previously generated or emailed copy. Requires View Sales Order permission. Returns 404 if the sales order does not exist or has been soft-deleted — unlike the in-app preview, which still renders a deleted order. New endpoint — no existing behavior changes.
Added Invoice PDF — GET /invoices/{invoiceId}/pdf, returning application/pdf as INV{number}.pdf. The PDF reflects the invoice's current state and the tenant's PDFs & Emails settings at the time of the request; it is not a previously generated or emailed copy. Date formatting is presentational and may vary by environment. Requires View Invoices permission. Returns 404 if the invoice does not exist or has been soft-deleted. New endpoint — no existing behavior changes.
Added Shipment PDF — GET /shipments/{shipmentId}/pdf, returning the shipment's packing slip as application/pdf. The file name is based on the order fulfilled by the shipment and the shipment's sequence number, rather than the requested shipment ID. For example, SO1862-2.pdf for a shipment fulfilling a sales order and PO104-1.pdf for outside processing. The PDF is available in every status, including for shipments that have been cancelled. Requires View Shipments permission. Returns 404 if the shipment or the order it fulfils does not exist or has been deleted. New endpoint — no existing behavior changes.
Changed qtyCompletedThisRun on Reporting Job Activity By Operator List to report what each operator reported, rather than the operation's run total. Completing an operation records that total against whoever closed it, and it previously replaced that operator's own reported quantity — on a run finished by one of several operators, the whole run was credited to the closer. Where anyone reported quantity on an operation, each operator is now credited only with what they reported; a close-out still stands in where nobody reported individually and it is the sole record. Per-operator quantities on such an operation fall, and need not sum to unitsCompletedOnOperation. unitsPerHour is derived from this field and moves with it. Request and response shapes are unchanged.
Added Purchase Order PDF — GET /purchase-orders/{purchaseOrderId}/pdf, returning application/pdf. The file name is based on the tenant's purchase-order title template, with Purchase Order abbreviated to PO and spaces removed. For example, the default template produces PO104.pdf for an ordered purchase order and RFQ104.pdf while it is still a draft. The same purchase order may therefore have a different file name as its status changes. Requires View Purchase Order permission. Returns 404 if the purchase order does not exist or has been soft-deleted. New endpoint — no existing behavior changes.
Changed quantityShipped on Shipment Line Items List to report what a line actually shipped: its packed quantity once the parent shipment has shipped, and 0 before then. It previously derived from the line's packingStatus, which is a separate value an operator can pin by hand and which shipping does not clear — so a shipped line whose packing status read NotPacked reported 0 even though its packed quantity went out and was invoiced, while a line packed on a shipment that had not yet shipped reported that quantity as already shipped. Consumers summing this field will see shipped totals rise where the first case applied and fall where the second did. quantityPacked, packingStatus, and shipmentStatus are unchanged, and the field now agrees with the shipped quantity reported by Reporting Shipping List.
Added salesOrderLinePrimaryKey to Reporting Sales Order Lines List rows — an opaque identifier unique to each row, stable within and across pages, for joining rows back to a line. The line-item number is not unique. Addition-only — existing payloads gain the field but no field changes.
Added dropship and dropshipCustomerPoNumber to the sales order. Returned by Sales Order Get and Sales Order List, and accepted by Sales Order Create, Update, and Patch. dropship marks the order as fulfilled by shipping directly to the end customer — the same flag as the in-app Dropship toggle on the deliverables timeline — and dropshipCustomerPoNumber carries the end customer's purchase order number, which prints on dropship paperwork and labels. On create, dropship defaults to false when omitted. On update the two fields move together and are written only when dropship is supplied: omit dropship and neither field changes, so an existing integration that keeps PUTting its current payload never turns dropshipping off and never clears a PO number authored in the app. Sending dropship with no dropshipCustomerPoNumber clears the PO number — that is the only way to clear it, since a blank string is coerced to null before validation rather than acting as a clear-this-field sentinel. Note the asymmetry: dropshipCustomerPoNumber does not follow the replace-on-PUT behavior of customerPoNumber and publicNote, which are cleared by omission. Addition-only — existing payloads gain both fields but no field changes.
Added isLotTracked and isNonInventory to Item Update, and isNonInventory to Item Create. Both values were already returned by item reads, and isLotTracked was already settable at create time, so until now neither could be corrected on an existing item. Like number, both are opt-in on update: omitting a field (or sending null) keeps the item's current value, so an update that does not mention them cannot change them, and a value that matches the item's current one changes nothing. isNonInventory also keeps the item's inventoried state in step, since a non-inventory item is never held in inventory. Only a buy item that is not sellable can be non-inventory. On create, anything else is rejected with a 400. On update the same 400 applies to turning it on, while an item that is already non-inventory can restate that value — so a read-modify-write update of a legacy item that is already in that state does not start failing. Addition-only: requests that omit the new fields behave exactly as before.
Added a shipped-date window to Reporting Shipping List: shippedDateFrom / shippedDateTo, bounding the date a shipment actually shipped — its ship-date override when set, else its recorded ship date. Distinct from the existing shipByDate bounds, which filter the date it was due to ship. Both bounds are day-inclusive, covering the whole to day whatever time a shipment went out, and match only shipments whose status is Shipped — an override can be set while a shipment is still open. Addition-only — existing requests are unaffected.
Added shipped-date and invoiced-date windows to Reporting Sales Order Lines List: shippedDateFrom / shippedDateTo, invoicedDateFrom / invoicedDateTo, and the dates they filter on — firstShippedDate, lastShippedDate, firstInvoicedDate, lastInvoicedDate — on every row. A line ships and is invoiced in tranches, so a bound pair selects lines whose activity span overlaps the window rather than only those wholly inside it; a line with no such activity carries nulls and is excluded by any bound on it. Bounds are day-inclusive. Addition-only — existing payloads gain the fields but no field changes.
Changed paging on Reporting Sales Order Lines List to a total order. Lines tying on every sort key — same ordered date, order number, line item, and unit price — previously ordered arbitrarily, so one could repeat on the next page or be skipped altogether. A unique line key now breaks those ties. Filters and row shapes are unchanged, but a caller paging the same query may see tied lines fall on different pages than before.
Added requiredQuantityLbs, collectedQuantityLbs, and outstandingQuantityLbs to Material Requirements Report rows — the existing kilogram quantities converted to pounds. Populated wherever requiredUnitOfMeasure is kg — always the case for material requirements, and also for an item requirement whose item is stocked by weight; null for quantities in any other unit, where a weight conversion would be meaningless, and null wherever the kilogram value is itself null (an unresolved nest weight stays unknown rather than becoming zero). The kilogram fields are unchanged. Addition-only.
Added Item Purchase History — returns every purchase of one item within a date window, newest first. Each entry carries the purchase order, vendor, ordered unit price, billed unit price, effectiveUnitPrice (the billed price when present, otherwise the ordered price), and quantities ordered and received. Includes a summary with min/max/average price, last price paid, last order date, and distinct vendor count. Counts only orders with status Ordered or Paid. months defaults to 12 and is clamped to 1–60. Not paginated — bounded by one item and the window.
Documented itemOrigin on Item Create. Use buy, make, makeOrBuy or customerSupplied; the schema also carries kit and none for historical reasons, and while both are accepted neither is supported for new items. No behavior change — the field has always accepted every value in its schema. To spell out the one that was hardest to discover: a customer-supplied item is created by sending itemOrigin: "customerSupplied". itemOrigin remains create-only, so an existing item's origin cannot be changed through the API — an item created with the wrong origin has to be replaced rather than converted.
Added reason to Scrap Report entries — a reference (id, name) to the reason selected when the scrap was recorded, or null when the entry carries none. The reason has always been captured with the entry and shown in the product; the report endpoint now returns it, so a consumer grouping scrap by cause no longer needs the Excel download. Addition-only — existing payloads gain the field but no field changes.
Fixed Item List failing with a server error whenever the descriptionFilter filter was supplied — the filter now runs in the database. An item matches when its description contains the supplied text, compared case-insensitively, and the filter narrows results alongside any other filter sent with it. Separately, the documented syntax for this field was wrong on both v1 and v2: it described quoted phrases for exact matching and a leading - to exclude a term, neither of which is implemented — v2 has matched on a plain case-insensitive substring since it was repaired, and v1 now does the same. Both field descriptions have been corrected; no v2 behavior changes.
Changed the due-window filters on Tool List: isOverdue, dueWithin7Days, and dueWithin30Days no longer match out-of-service tools — tools that are inactive with any reason other than OutForCalibration: Lost, Damaged, RequiresRepair, Retired, or a legacy row with no recorded reason. Such a tool no longer needs calibration, so it no longer counts as due or overdue. A tool that is inactive with reason OutForCalibration still matches, because it is inactive only while it is being calibrated. This matches the in-app Calibration Overdue / Due 7 Days / Due 30 Days KPIs, which now exclude the same tools. Request and response shapes are unchanged — only the filtered row set changes.
Added measurementRangeMinPrecision and measurementRangeMaxPrecision to tools — the number of decimal places each measurement-range bound was entered with, matching the existing accuracyPrecision. A range entered as 0.1000–0.4000 now keeps its trailing zeros on the tool and on generated calibration certificates instead of collapsing to 0.1–0.4. Returned by Tool Get (and echoed by the create/update responses), and accepted by Tool Create and Tool Update. Omitted or null means no precision was recorded, and the values render at their natural precision as before. Addition-only — existing payloads gain the fields but no field changes.
Added progress-billing line items to invoice reads: Invoice Get and Invoice List now return two new line-item type values, ProgressBilling (an installment invoice's percentage-of-order charge) and ProgressBillingAdjustment (the final invoice's reversal of one already-billed installment). Both carry quantity 1, the line's amount in price and discountedPrice, the line's accounting code, and isTaxablefalse. Addition-only for existing consumers: these lines exist only on invoices generated from a progress-billing schedule, and invoices without one are unchanged.
Added needsQualityPlan to item reads — a boolean indicating whether the item requires an approved quality plan. Combined with the existing qualityPlanStatus, a consumer can distinguish an item that has an approved plan (needsQualityPlantrue and qualityPlanStatusApproved) from one that needs no plan at all (needsQualityPlanfalse), which the status alone could not express. Returned by Item Get, Item List (including its v2), Item Update, and Item Patch. Read-only — returned on these reads and mutation responses but not settable on create/update. Addition-only — existing payloads gain the field but no field changes.
Added a names filter to Shipment List, matching shipments by the shipment number shown in the product (e.g. SHP-SO1234-1, SHP-PO5678-2). Resolving a shipment from a number previously meant looking up the parent sales or purchase order and walking its shipments; a number now resolves in a single call. Accepts up to 500 names, matched exactly and case-insensitively; sending an empty list (or omitting the field) returns unfiltered results as before. Addition-only — existing requests are unaffected.
Fixed Receipt List failing with a server error whenever the externalReference filter was supplied — the filter now runs in the database, for both purchase-order and sales-order receipts. Filter semantics are unchanged: a receipt matches when it carries the named external-reference key and, where type or externalId are also supplied, that same entry matches them.
Fixed Item List failing with a server error whenever the customField filter was supplied — the filter now runs in the database. An item matches when one of its custom fields is exactly the requested key:value pair, compared case-insensitively; partial key or value fragments do not match.
Added onFair to the full-routing in-process-tracking endpoints — get, list, and create for jobs, quotes, and sales-order part line items. It marks a checkpoint as reported on the First Article Inspection Report (FAIR), a meaning that until now was carried by firstArticle. The two are independent from here on: firstArticle remains the sampling instruction (it still resolves an unsupplied frequency to 0), while onFair governs FAIR membership only and never affects sampling. onFair defaults to false on create, so an integration that wants a checkpoint reported on the FAIR must set it explicitly — firstArticle: true on its own no longer implies FAIR membership. Reads are addition-only: existing payloads gain the field but no field changes.
Fixed Time Clock List failing with a server error whenever startedDateRange or stoppedDateRange was supplied — the range filters now run in the database. Filter semantics are unchanged: a still-running entry (no stop time) is treated as unbounded, so it matches any stoppedDateRange.start and is excluded by any stoppedDateRange.end.
Fixed Purchase Order Get and List returning a spurious contactId for purchase orders that have no contact — the field now reads null for contactless purchase orders, and the schema declares it nullable. Purchase orders with a real contact are unaffected; consumers that assumed the field always holds a value should treat it as optional.
Fixed Purchase Order Outside Processing Line Item Create failing with a server error when the targeted job operation carried no item reference or no linked system operation — the line item is now created from the fields that are present, and outside-processing line-item reads return itemId as null (now declared nullable) for such lines. Requests naming complete job operations are unaffected.
Added Job In-Process Tracking Response List — lists a job's in-process tracking checkpoints together with every recorded operator reading (stable reading id, numeric/boolean/text value, the operator plus separate recordedBy/updatedBy attribution, the measurement tool, and timestamps), optionally filtered by phase. Each checkpoint also reports its drawing and recording units and the requirement expressed in both (targetValue/minimumValue/maximumValue are in drawing units; recordingTargetValue/recordingMinimumValue/recordingMaximumValue and the readings are in recording units). measurementToolNextCalibrationUtc is the tool's current next-calibration date (live, not snapshotted at reading time). Previously only checkpoint definitions were exposed (via the routing endpoints); this returns the readings actually captured during production. The result is intentionally single-job-bounded and is not paginated — the readings axis is not independently bounded — and an unknown job id returns 404.
Fixed Sales Order Refund Line Item Create and Update mishandling the order's receiving record when adding or removing a return: on some orders a refund-with-return failed with a server error (a dangling receiving reference), and on others the order's receiving record was silently replaced — detaching previously recorded receipts from the order. Both endpoints now load and mutate the order's existing receiving record. Request and response shapes are unchanged.
Fixed Note Get failing with a server error for every request — the endpoint's channel lookup sat on an unimplemented legacy query path, so no call could ever succeed. It now behaves as documented: 200 with the note for a valid id, 404 when no note has that id. Request and response shapes are unchanged.
Changed Quote Status Update: transitioning a quote to Sent now stamps its quotedDate with the send time, and — only when the quote's expiration still sits at its create-time default (the configured quote-expiration window measured from the quoted date) — recomputes expiration from the newly stamped date. An expiration that was set deliberately is never moved. Marking an already-Sent/Won/Lost quote Sent remains a no-op and changes neither field. Integrations that write a historical quotedDate and then mark the quote Sent should reverse the order — set the status first, then write the dates (both fields remain writable afterward).
Added isMaterialLine to routing input item reads — true when the line is tied to an input material (created by a material selection) rather than being a plain component line. Returned by Item Routing Input Item List, Item Routing Input Item Get, the operation-item reads, and the job, quote, and sales-order full-routing input item endpoints. Addition-only — existing payloads gain the field but no field changes.
Fixed Item Routing Operation Batch leaving input materials and input items associated with operations the batch had removed. Steps omitted from the request now release those associations (routingStepId returns to null), matching single-operation deletion. The endpoint also returns a 400 naming the id when a well-formed systemOperationId matches no operation in system data, where it previously failed with a 500.
Improved the rejection for picking material held for incoming inspection via Job Operation Pick. Picking a lot reserved for an incoming or failed inspection has never been allowed; previously a pick naming such a lot by id failed with the generic "No inventory for the given item, location, and lot." Requests now fail with the descriptive rejection naming the lot and the hold (e.g. Lot {number} is being held for incoming inspection and cannot be picked.), matching the in-app behavior. Still a 400; only the message is more specific — consumers should match on status, not message text.
Inventory Override now rejects an override that would reduce the on-hand quantity of a lot held for incoming inspection (or one that failed inspection), with a 400 naming the lot and the hold — quarantined material can only leave inventory through the inspection workflow (release, or rollback of the receipt). Overrides that keep or increase the held quantity are unaffected, as are all overrides on unreserved and job/sales-order-reserved lots.
Fixed price on sales order discount line items reading as 0. The calculated discount amount (and the derived subTotal/preDiscountSubTotal/discountedPrice on the generic line-item shape, plus absoluteAmount on the discount line item) is now computed from the sales order, so percentage and absolute discounts return their real value for every sales order. Percentage discounts are calculated over the order's part and blanket lines, matching the sales order total. Affects the Sales Order Discount Line Item and Sales Order Line Item endpoints.
Added accountingHeldUtc to Receipt List and Receipt Get responses. The field is set while a receipt's accounting is deferred because it contains material held for incoming inspection (tenants using the new after-inspection invoice-timing mode) and is null otherwise — including for every receipt on tenants that keep the default on-receipt mode, where nothing changes. While a receipt is held, Receipt List excludes it; it appears (with accountingHeldUtc null) once its inspections complete. Receipt numbers are stable across the hold — a held receipt keeps occupying its position, so siblings' numbers never shift when it is released. Receipt Get by id remains unfiltered and returns held receipts with the field set. Consumers that reconcile bills from the receipt list should treat a held receipt exactly like one that hasn't been received yet: it will surface in the list when it becomes billable.
Added ten values to the tool type enum: CmmDatumSphere, ThreadMicrometer, InsideMicrometer, PinMicrometer, SetPlug, NptRingGage, NptPlugGage, GrooveMicrometer, WeldingEquipment, and SurfaceTesterMaster. Returned by Tool Get, Tool List, and tool calibration reads, and accepted by Tool Create, Tool Update, and the Tool List types filter. Addition-only, but consumers that map the enum should tolerate the new values.
Fixed Item Routing Input Item Delete and Item Routing Input Item Update not applying to the copy of the input item carried on its routing operation. Delete removed only the routing-level line, so the item still appeared on the operation (and in the UI) after a 200; update left the operation copy with stale values. Both endpoints now apply to the operation copy when it can be identified unambiguously — by shared id, or by material for lines stored with a separate id (such as those added through Item Routing Operation Item Create) when the line is the routing's only line for that material. Deleting a line that aggregates one material across several operations removes all of its operation copies; updating such a line changes only the routing-level quantity, leaving the per-operation split intact. No request or response shape changes.
Added expirationUtc to Auth Validate — the UTC instant at which the calling token stops being accepted, so an integration can read its own key's remaining life instead of tracking it out of band. Always a future instant, since an expired token is rejected before it reaches the endpoint. A token created with no expiry reports the maximum representable date rather than a real one. Addition-only — existing payloads gain the field but no field changes.
Added optional skip and take query parameters to Item Routing Input Material List. Addition-only — requests that omit them keep receiving the full list, unchanged.
Added requiredStockPieces to Reporting Material Requirements List — the stock pieces (nests) needed to produce the row's jobPlannedQty, alongside the existing required weight. Null on item requirement rows, and null when the material's nest yield is unknown, so null means not known rather than none needed. The value is fractional, since the unconsumed part of a piece returns to stock as a remnant — round up when sizing a purchase or a pick. Each row is scoped to one operation, so do not sum the field across a job's operations. Addition-only — existing payloads gain the field but no field changes.
Fixed price on purchase order discount line items reading as 0. The calculated discount amount (and the derived subTotal/preDiscountSubTotal/discountedPrice on the generic line-item shape) is now computed from the purchase order, so percentage and absolute discounts return their real value for both new and historical purchase orders. Percentage discounts are calculated over the order's part and outside-processing lines, matching the purchase order total. Affects the Purchase Order Discount Line Item and Purchase Order Line Item endpoints.
Added employeeId to User List results. User Get has returned the field since it was introduced, but the search response omitted it, so resolving an employee identifier for a set of users meant following every search with a per-user get. Addition-only — existing consumers are unaffected, and users with no employee identifier return null as they do on User Get.
Added optional includeDeleted to User List. When true, inactive/deleted users matching the search are returned (with deleted: true); omitting it (or sending false, the default) preserves the current active-only behavior. Previously a deactivated user was excluded from search results even when queried explicitly by email — inconsistent with User Get, which already returns deleted users. Addition-only — existing consumers are unaffected.
Added the tool calibration write surface (no sign endpoint — signing a calibration record remains a human act performed in the product — and no delete endpoints). Tool Calibration Create records a calibration: an external vendor or customer attestation, or an in-house calibration with per-checkpoint responses measured against the tool's current template. The server derives the As-Found/final results from the measurements and rejects a result that disagrees, pins the template version, stamps the measurer, and attributes customer-source records to the tool's owning customer; reference gages take no records. A save recording a new failure automatically opens an NCR, echoed as createdNcr. Tool Calibration Update edits an unsigned record (signed records are immutable) as a full replacement with re-derivation, guarded by optimistic concurrency: reads of a record by id now return an opaque version token, the update requires it, and a stale token is rejected with 409. Tool Create and Tool Update manage tools (identity, type, status, calibration schedule, ownership); a calibration frequency of none creates a reference gage, and outForRecalibrationDateUtc (also added to Tool Get) marks a tool out for recalibration in tandem with the outForCalibration inactive reason. Tool Calibration Template Create publishes a new append-only template version (checkpoints, tolerances, trials, environment ranges, master gauges); existing records stay pinned to the version they were measured against. Record writes require the Perform Calibration permission; tool and template writes require Edit Tool Template — mirroring the in-app permission model. Creates are not idempotent: a request retried after a timeout creates a second record (and a second NCR for a failing calibration) — list before re-creating.
Added Job Operation Complete, completing a job operation the way the shop floor does: closes any open timers on the operation, records an optional final quantity and scrap, marks the operation complete, and readies downstream operations. When the completed operation is the item's last (or a split operation producing finished goods), goods are received into inventory (with optional location, lot number, and expiration; set addItemsToInventory to false to skip) and the item — and the job, once every item is done — is completed. Complements Add Quantity Completed, which records production without completing; quantity passed to complete is added on top of previously recorded quantity.
Added a read-only rates block to Operation Get, Operation List, and Operation Update responses, carrying the operation's hourly cost rates (setup, labor, overhead, machine) and hourly shop rates (setup, run, machine) in the shop's primary currency. A null rate inside the block means no operation-level rate is set and the work-center rates apply. Addition-only — existing payloads gain the new block but no field changes.
Added the tool calibration read surface. Tools: Tool Get returns a tool's identity (number, rendered T-{number} for display), serial number, type, status, calibration schedule, computed nextCalibrationDateUtc, and its latest calibration outcome (lastCalibrationResult, lastCalibratedBy); Tool List additionally returns the derived calibrationStatus the tools grid displays, with filters for search, types, statuses, due windows (isOverdue, dueWithin7Days, dueWithin30Days), reference gages, and modifiedAfterUtc for incremental polling of created/edited tools. Templates: Tool Calibration Template Latest, Versions List, and Version Get expose the checkpoints, tolerances, trial counts, environment ranges, and master gauges a calibration is measured against, plus each version's effective range. Records: Tool Calibration Get and Tool Calibration List return calibration history — results, As-Found/As-Left measurements by checkpoint and trial, the pinned template version, and sign state — filtered by tool, vendor, result, type, and date range. Tool Calibration Certificate downloads a record's certificate PDF (the exact artifact frozen at signing once a record is signed). All endpoints require the View Tool Calibration permission on user-bound tokens.
Added optional number to Item Update to support renaming items. Omitting the field (or sending null) keeps the current number. A rename enforces number uniqueness (400 on collision) and updates every reference to the item — BOM inputs, usage, purchase orders, and the system lot. Uniqueness is scoped to number + revision, so when renaming an item with multiple revisions, rename every revision of the family.
Added Reporting Material Requirements List endpoint returning per-operation required items and materials for scheduled jobs, with resolved required quantities (items in their unit of measure, materials as a weight in kilograms) plus collected and outstanding amounts. Lets consumers report material demand across scheduled operations in a single paged call instead of fanning out per job to routings, items-to-make, and operations.
Added workOrderId, workOrderName, workOrderOperationId, and workOrderOperationName to the timer response for List Job Tracking Timers and List Timers. Timers tracked against a work order now surface the work order and its operation names, which were previously populated only for job-tracked timers. This is an addition-only change — existing consumers are unaffected.
Fixed price on invoice discount line items reading as 0. The calculated discount amount (and the derived subtotal/discountedSubtotal on the generic line-item shape) is now computed from the invoice, so percentage and absolute discounts return their real value for both new and historical invoices. Affects the Invoice discount line item and line item endpoints.
Added item tag management endpoints: Item Tag Attach, Item Tag Detach, and Item Tags Replace. Attach and detach are idempotent; replace accepts the item's full tag set (an empty list clears all tags). Each returns the item's resulting tags, complementing the existing Item Tag List read endpoints.
Sales-order date fields (orderedDate, dueDate, productionDueDate, deliveryDueDate, and part-line-item deliveryDate) now use the calendar date exactly as written, ignoring any time-of-day and UTC offset in the supplied value. Previously an offset-bearing timestamp could roll the stored date to an adjacent day. Affects Sales Order Create, Update, and Patch, plus Sales Order Part Line Item Create, Update, and Patch. Consumers sending date-only values or midnight-UTC timestamps see no change.
Added Timer Create endpoint for recording completed setup, labor, and machine timer blocks with explicit startedOnUtc and stoppedOnUtc timestamps — for time captured outside Fulcrum (backdated up to 30 days).
Removed the incorrect minLength: 1 published on optional description fields across item, quote, sales-order, purchase-order, and invoice schemas. The server has always accepted empty descriptions; the published schema now agrees, so schema-validating clients no longer reject them.
Operation Get and Operation List now tolerate operations stored with a time option but no time unit, returning null time blocks for them instead of failing the whole request.
Added shipByDateBefore, shipByDateAfter, shippedDateBefore, and shippedDateAfter filters to Shipment List.
Added salesOrderId and purchaseOrderId filters to Shipment Line Items List; the line-item response now also carries shipmentStatus, shipByDate, shippedDate, and shippedDateOverride from the parent shipment.
Added calculated shipByDate to Sales Order Get, List, and Update responses — the earliest ship-by date across the order's non-cancelled shipments, falling back to the earliest delivery due date minus the customer's shipping lead time.
Added modifiedAfterUtc and modifiedBeforeUtc filters to Job List and Operation List for incremental polling of changed records.
Purchase Order Outside Processing Line Item Update now resyncs open outside-processing shipment quantities to follow the purchase-order line quantity when the update changes it, rather than leaving them at the job's total quantity.
Added netValueChange to inventory-event change details on Inventory Event Get and Inventory Event List. For stock takes it carries the true value delta of the adjustment, which the existing valuation buckets don't capture.
Fixed Job Tracking Get failing for jobs whose cost breakdowns contain legacy overhead rows; the cost-breakdown costType value set gained overhead and unknown. Also fixed componentLabor cost-breakdown lines reporting the material per-unit cost instead of the labor per-unit cost.
Added rework information to Job Operation List: isRework and hasAssociatedRework flags, plus a rework object with the reason, notes, quantities, and creator.
Corrected error status codes across the API: requests referencing records that don't exist now return HTTP 404, and business-rule validation failures return HTTP 400 with the reason in the response body — many of these cases previously returned HTTP 500.
Added Scrap Report endpoint returning scrap entries (item, quantity, value, operator, operation, work center, department, equipment, and job) over a date range.
Added Item Customer endpoints for managing customer-specific item data — customer item number and name, with read-only customer price breaks: Create, Update, List, and Delete.
Added item price-break endpoints: Sales Price Breaks List and Upsert for base, customer-specific, and customer-tier pricing (fixed unit price or margin), and Purchase Price Breaks List and Upsert for vendor costs. Upserts replace the matched price break's full set of quantity break points.
Added v2 Material Get, Material-Vendor List, Material-Vendor Create, and Material-Vendor Update endpoints that accept identifiers in the request body instead of the URL path. The original path-based endpoints are now marked obsolete; prefer the v2 variants when material identifiers may contain reserved URL characters such as /.
Added createdFromQuoteId to Sales Order Get and Sales Order List so consumers can resolve a sales order's originating quote without scanning the full quote list.
Removed operation references from cost-breakdown lines and timers. Cost breakdown lines now expose a flat referenceId and referenceName instead of a nested operation object. Affects all endpoints that return Cost Breakdown data.
Purchase Order Part Line Item Patch now bypasses status validation when only externalReferences, promiseDate, or receiveByDate are being modified, matching the existing behavior on the parent purchase order.
Dereferenced item and location on Inventory Transactions List. The transaction response now exposes the related item and location identifiers directly instead of through a nested reference object.
Removed the modifications field from MaterialShapeDto (affects Material List and Material Get). The field has been unpopulated since the v3 material schema rolled out; use type, spec, subspec, and finish instead.
Added discountAmount (calculated total) to discount line items on invoices, sales orders, and purchase orders. Affects Invoice, Sales Order, and Purchase Order endpoints that surface discount lines.
Added enableRemnantSync to Item Get and Item List so ProNest integrations can opt items in or out of remnant sync.
Reshaped the NCR DTO: split impact information into a dedicated impact object, expanded department into a full ReferenceDto with id, and added user-reference mappings. Existing fields remain available; consumers reading impact* fields directly should migrate to the nested impact object.
Added attachMaterialsToOperations toggle to Operation Create for Paperless Parts integrations that need to attach nestable materials to operations on creation.
Fixed quantity validation on invoice line item refunds — zero quantities now reject up front. Affects Invoice Line Item endpoints.
Replaced outsideProcessingTime with outsideProcessingCost on operation DTOs returned and accepted by Operation Create, Operation Get, and the operation entries inside item-routing endpoints. Consumers using outsideProcessingTime for outside-processing operations must migrate.
Added unit specifiers (widthUnit, heightUnit, lengthUnit, thicknessUnit) to Material Activate so metric and imperial materials can be created without ambiguity.
Added customerDetails to Item Get and Item List V2, and expanded vendorDetails to include price breaks. Set includeCustomerData=true on the list endpoint to opt in.
Added createdBeforeUtc, createdAfterUtc, modifiedBeforeUtc, and modifiedAfterUtc filters to Sales Order List.
Job Create now routes sales-order-based job creation through the full sales-order job-creation pipeline so all ancillary data is gathered correctly when supplying salesOrderId and salesOrderLineItemId.
Inventory Receive now accepts machineValue, laborValue, materialValue, and outsideProcessing valuations, and accepts either lotNumber or lotId (creating the lot if the supplied number doesn't exist).
Tightened Attachment Create and Remote Attachment Create to always treat attachments as Standard. The attachmentType field has been removed from the request — use the new certification endpoint for certification uploads. The accepted owner-type set has also been narrowed (e.g. InventoryLot is now reserved to certifications).
Added sourceType, relatedEntityType, and relatedEntityId as filter parameters for [Inventory Transactions List](/api-schema#tag/Inventory-Transactions/operation/
ListInventoryTransactions)
Added JobTracking Get for retrieving job progression and status information. This can be used to get a bird's-eye view of the job, including current operation(s), next operation(s) and estimates vs actuals so users have visibility into their shop floor.
Added Job Operation List for retrieving all operations for a job with related item to make and operation details.
Added OriginalScheduledStartUtc, OriginalScheduledEndUtc, ScheduledStartUtc and ScheduledEndUtc to all endpoints that return jobs. Affects Job List, Job Get and JobTracking Get
Added Item Routing Operation Batch for bulk applying operations to an item. Of note, this is geared towards nuance associated to continuous flow whereby operation order must be unique (no overlaps) but it can still be used if continuous flow is not enable. This will allow for bulk re-ordering operations, updating the existing operations where applicable, adding operations where they do not currently exist and removing no longer applicable operations.
Deprecated the status field in favour of statuses in the request body schema for [job List][api-schema#tag/Job/operation/ListJob] so that more than 1 job status can be filtered for at a time. status is Scheduled for removal EOD 2023-10-31.
Added the hasIncompleteOperations in the request body schema for [job List][api-schema#tag/Job/operation/ListJob] to filter for jobs with incomplete operations.
Deleted indicators added to Sales Order, Invoice and Purchase Order objects. While /list endpoints do not return deleted entities, you can still GET a deleted entity by it's Id. Having an indicator on the object will help your applications decide whether they can still make changes to that entity.
The Items List endpoint has been versioned. The new V2 Item List endpoint has a more robust item number matching options than the simple "contains" method of the original endpoint.