URL class: Inventory — /Windward/WebAPI/Inventory/...

Source of truth: ServerMethodsInventory.pas, CM_Inventory.pas, AO_Inventory.pas, CM_STA.pas, CM_AltSupply.pas, CM_Barcodes.pas, AO_Part_Prices.pas.

The older parts operations on TServerMethodsWebAPI (Get_Parts, Get_Parts_V2, List_Parts, Parts_Read, Parts_Insert, Parts_Update, Get_Stock, Get_Kits, Perform_Stock_Adjustment, the superseding-parts and part-image operations) are covered in The Legacy TServerMethodsWebAPI Surface.


Operations

VerbPathDelphi methodEnvelope
GET/Inventory/Inventory_HandshakeInventory_Handshakead hoc
GET/Inventory/GetInventoryRecordCountGetInventoryRecordCountad hoc
GET/Inventory/GetPriceCodesGetPriceCodesad hoc
GET/Inventory/Inventory/{InventoryId}InventoryAPIResponse + Inventory
GET/Inventory/InventoryChangesInventoryChangesAPIResponse + Inventory
POST/Inventory/addInventoryupdateaddInventoryAPIResponse + Inventory

POST /Inventory/addInventory

The two things to know before you start

First. A part that cannot be matched or inserted is reported as a success. Unlike every other domain, inventory's no-op branch sets ActionSuccess := true:

{ "RecordId": "0",
  "RecordDesc": "Operation = [No Op] Invalid Data provided!",
  "ActionResult": "",
  "ActionSuccess": true }

or, where the matcher built a description:

{ "RecordDesc": "Category 01.02, Part: WIDGET-1, Item: , SuppPart: ",
  "ActionResult": "Category *and* one of the part numbers required to insert new record; Inventory record skipped",
  "ActionSuccess": true }

You must inspect RecordId and ActionResult, not just ActionSuccess, on this endpoint. A skipped part has RecordId of "0" and an ActionResult containing skipped, or a RecordDesc beginning Operation = [No Op].

Second. To insert a new part you must send both SubCategory and PartNumber. Anything less is skipped. See the match cascade in The Upsert Model.

Request shape

{
  "ConnectionInfo": { "TerminalNumber": 1 },
  "Inventory": [
    {
      "InventoryId": 0,
      "SubCategory": "01.02",
      "PartNumber": "WIDGET-1",
      "ItemNumber": "",
      "SupplierPartNumber": "SUP-991",
      "VendorId": 42,
      "Description": "Blue widget",
      "Description2": "",
      "UnitOfMeasure": "EA",
      "Wholesale": 12.50,
      "Freight": 0.75,
      "Duty": 0.00,
      "Extra": 0.00,
      "Landed": 13.25,
      "Weight": 1.2,
      "eCommerce": true,
      "MarkedDeleted": false,
      "Prices": [
        { "Department": 0, "Level": 1, "RegPriceCode": "F", "Regular": 24.99 }
      ],
      "Barcodes":  [ { "Barcode": "0123456789012", "SupplierId": 42 } ],
      "AltSupply": [ { "SupplierId": 77, "SupplierPartNumber": "ALT-1", "Cost": 12.10 } ],
      "STA":       [ { "STANumber": "SPRING26", "Startdate": "2026-03-01T00:00:00" } ],
      "InventoryFreeFormGroup1": [ { "FreeFormID": 1, "FreeFormData": "24 months" } ],
      "InventorySellingComment": "Long selling copy",
      "InventoryWebComment": "Web copy",
      "InventoryInvoiceComment": "Prints on the invoice"
    }
  ]
}

Supplier resolution

The supplier is resolved in this order:

  1. VendorId — used only if it resolves to a real supplier account (IsValidSupplierID). An invalid id is ignored silently.
  2. If no supplier is set yet, VendorName is looked up by exact business name on accounts of type P. No fuzzy matching, no partial match.

On update, the supplier may be locked. A [System Five Web API Service] INI setting, Update Inventory Supplier, controls whether an update is allowed to change a part's main supplier:

SettingBehaviour on update
Y (default)VendorId / VendorName in the payload replaces the part's supplier
NThe supplier is left unchanged, silently. Your payload's supplier is ignored.

Inserts always set the supplier. There is no response field telling you which mode the server is in — if supplier changes appear not to stick, this setting is the reason.

Brand

BrandName is resolved through the same supplier-name lookup. An empty or unrecognised brand name leaves the existing brand alone rather than clearing it — there is no way to clear a brand through this endpoint.

Costs

Wholesale, Extra, Freight, Duty and Landed are applied to cost tier 1.

Note the asymmetry:

  • On insert, costs are applied twice — once by the general field population and again by ProcessCosts after the virtual-warehouse lookup. This is deliberate: the virtual warehouse can overwrite cost, and ProcessCosts puts your values back.
  • On update, ProcessCosts does not run, and the virtual-warehouse lookup does not run either. Costs are applied once.

Virtual warehouse (insert only)

On insert, after the identifying fields are populated, UpdateFromVirtualWarehouse looks the part up in the virtual-warehouse/transfer table by its identifiers and copies matching data onto the new part. This means a newly inserted part can come back with values you did not send. It does not happen on update.

Prices

Prices is an array; each entry targets one price level in one department:

FieldTypeNotes
DepartmentintegerRequired — an entry without it is skipped entirely
LevelintegerRequired, and must satisfy 0 <= Level < System5.PriceCount
RegPriceCodestringPrice calculation method, first character only
RegularnumberThe regular price, when the code is a money code
RegPricePercentnumberThe regular percent, when the code is a margin/markup/discount code
SalePriceCodestringAs above, for the sale price
Salenumber
SalePricePercentnumber

The rules that matter:

  • The price code decides which field is read. Money codes read Regular / Sale; margin, markup and discount codes read RegPricePercent / SalePricePercent.
  • If you omit the price code, the part's existing code is used, so the same payload can behave differently on two parts.
  • If the expected field is missing, the code falls back to the other one and derives the value. For $ markup from landed and $ markup from cash price schedule, the percent value is added to landed cost / cash price to produce the effective price before comparison.
  • A value is written only when it differs from the current value. Sending the same price twice is a genuine no-op — the part's price-change date does not move.
  • Negative values are rejected by the level helper (>= 0 required).
  • Where the value is a percent and the dataset has "exclude inc-tax on retail" behaviour (Australian datasets with SQ_ExcludeIncTaxOnInvRetail), the tax-exclusive schedule is written instead of the normal one. The same payload therefore behaves differently on an Australian dataset.
  • After the array is processed the part's cached price-schedule list is cleared, forcing System Five to recompute.

Call GET /Inventory/GetPriceCodes to discover the valid code characters and their descriptions for the dataset before writing prices.

Comments — three different stores

FieldStored as
InventorySellingCommentComment record on the inventory file
InventoryWebCommentComment record on file number inventory + 1000
InventoryInvoiceCommentComData record on the inventory file

They are separate stores with separate lifecycles; writing one does not affect the others.

Child collections

STA, AltSupply and Barcodes are child arrays, processed through PutChildRecords.

None of them produce result entries. They can fail silently. Read the part back with DetailedResponse=Y if they matter.

Behaviour specific to each:

  • Barcodes default to insert. The matcher deliberately assumes a new barcode unless AltSupplyId is supplied, because a part may legitimately have many barcodes per supplier. It does scan for an identical barcode value to avoid exact duplicates. Re-posting a part with its barcode list will otherwise accumulate rows.
  • AltSupply matches on AltSupplyId, else on part + supplier + record type. SupplierName may be sent instead of SupplierId; the lookup is exact match only.
  • STA matches on STAId, else on part + active + Startdate (+ STANumber when supplied).

Free-form fields

InventoryFreeFormGroup1 and InventoryFreeFormGroup2 are arrays of { "FreeFormID": n, "FreeFormData": "..." }. Ids are dataset-specific; use ?FreeFormNameMap=Y on a GET to discover which id maps to which label.

Order of operations

Update: populate fields -> prices -> free-forms -> STA -> alt supply -> barcodes -> save.

Insert: create (via GetDefaultInventryCat to pick the creation mode) -> lock -> populate fields -> virtual warehouse -> costs -> prices -> free-forms -> STA -> alt supply -> barcodes -> save.

If part creation itself fails, the message is whatever System Five's CreateErrorMessage produces, or:

Failed to Add Part: {message}

ActionResult values

ValueMeaning
Inserted RecordCreated. Note: no "subject to System 5 verification" suffix here, unlike invoices and customers
Updated RecordUpdated
An error occurred during the insert of the recordBtrieve write failed
An error occurred during the update of the recordBtrieve write failed
No Part or Item or Supplier Part Number provided. ...Skipped — no identifier
Category *and* one of the part numbers required to insert new record; ...Skipped — insufficient data to insert
Failed to Add Part: ...Category/creation-mode resolution failed

GET /Inventory/Inventory/{InventoryId}

{InventoryId} of 0 returns all parts. Paginate.

Query parameters

ParameterNotes
FieldsComma-separated names from the response model
MarkedDeleted=YInclude parts marked for deletion. Default excludes them
eCommerceExport=YOnly parts flagged for e-commerce export
PriceLevel={n}A specific price level, or -1 for all levels
Department={n}Stock, prices and sale dates for that department, or -1 for all. Requires Departmental Inventory to be enabled in the dataset
CurrencyCode={n}Prices and costs in that currency. Requires Multi-Currency to be enabled
FreeFormNameMap=YAdds the FreeFormHeaders id-to-label map
PageSize / PageNumberBoth or neither; PageNumber is 1-based

PriceLevel, Department and CurrencyCode are parsed with StrToInt — a non-numeric value produces a conversion error in the response body, not a sensible default.

The Department and CurrencyCode parameters do not fail loudly when the corresponding System Five feature is not configured; you simply get the default data. If departmental figures look wrong, confirm the feature is enabled in the dataset before debugging the call.

Response

Array key Inventory. Records include identity, description, costs, Prices (one entry per level/department), InStock, StartSaleDate / EndSaleDate, AltSupply, Barcodes, STA, the free-form groups and the three comment fields.

Pagination uses the same forward-scan approach described in Customers — deep pages are progressively more expensive and hold the global lock.


GET /Inventory/InventoryChanges

Parts changed on or after EffectiveDateTime, via System Five record-state tracking.

Same prerequisites and same envelope-switching behaviour as GET /Customer/CustomerChanges — see Customers. Specifically:

  • Record state tracking must be enabled, or you get the legacy envelope with Record State tracking has not been enabled for this database; ....
  • An unparseable date returns the legacy envelope.
  • No changes returns the legacy envelope with a Success response.
  • Only a non-empty result uses the APIResponse + Inventory shape.

Accepts PriceLevel, eCommerceExport, Department, CurrencyCode, PageSize and PageNumber in addition to EffectiveDateTime.

This is the correct endpoint for ongoing catalogue synchronisation. It pages the change list rather than the parts file, so it does not suffer the forward-scan cost.


GET /Inventory/GetInventoryRecordCount

{ "SystemFive API Record Count": "Inventory", "Record Count": "48213" }

The count includes parts marked for deletion, regardless of the MarkedDeleted parameter. If you page Inventory/0 without MarkedDeleted=Y, this count will be higher than the number of records you receive — do not use it to compute page counts for a filtered fetch.

Like the customer count, it is computed by walking the file.


GET /Inventory/GetPriceCodes

Returns the price calculation methods configured for the dataset:

{
  "SystemFive API Price Codes": "Inventory",
  "F": "Fixed",
  "M": "Margin",
  "U": "Markup",
  "...": "..."
}

An ad hoc shape: the code characters are top-level keys of the same object as the marker pair, not an array. Iterate the object's keys and skip SystemFive API Price Codes.

Call this before writing Prices, since the valid codes and their meanings are dataset-configurable and RegPriceCode / SalePriceCode take only the first character.


Field reference

Identity

FieldTypeNotes
InventoryIdintegerMatch key 1
PartNumberstringMatch key; required with SubCategory to insert
ItemNumberstringMatch key
SupplierPartNumberstringMatch key
SubCategorystringSystem Five ledger-style category, e.g. 01.02. Required to insert
VendorIdintegerSupplier account unique; ignored if invalid
VendorNamestringExact-match supplier lookup, used only when VendorId did not resolve
BrandId / BrandNameinteger / stringBrand; unknown names are ignored, never cleared

Description and physical

Description, Description2, UnitOfMeasure, Size1, Size2, Size3, Measurement1, Measurement2, Weight, PackSize

Costs

Wholesale, Extra, Freight, Duty, Landed (all numbers, cost tier 1)

Flags

FieldType
MarkedDeletedboolean
eCommerceboolean

Prices (Prices[])

Department, Level, RegPriceCode, Regular, RegPricePercent, RegPriceDesc, SalePriceCode, Sale, SalePricePercent, SalePriceDesc, PriceLevelName

Comments

InventorySellingComment, InventoryWebComment, InventoryInvoiceComment

Child collections

STA[], AltSupply[], Barcodes[], InventoryFreeFormGroup1[], InventoryFreeFormGroup2[]

Read-only in responses

InStock, StartSaleDate, EndSaleDate, LastPriceChg, KitType, InventoryFreeFormHeaders, PriceCodes