URL class: VirtualInventory — /Windward/WebAPI/VirtualInventory/...
Source of truth: ServerMethodsVirtualInventory.pas, CM_VirtualInventory.pas, AO_VirtualInventory.pas.
What virtual inventory is
Virtual inventory is a supplier catalogue: parts a supplier can sell you that do not exist as stocked inventory records in System Five. It is stored in the transfer file and keyed by the pair (supplier, supplier part number).
Its main job is to feed part creation: when a real inventory part is inserted through POST /Inventory/addInventory, the service looks the identifiers up in the virtual warehouse and copies matching catalogue data onto the new part (see Inventory). Loading a supplier's catalogue here first therefore enriches every part you subsequently create from it.
Operations
| Verb | Path | Delphi method | Envelope |
|---|---|---|---|
| GET | /VirtualInventory/VirtualInventory_Handshake | VirtualInventory_Handshake | ad hoc |
| GET | /VirtualInventory/GetVirtualInventoryRecordCount | GetVirtualInventoryRecordCount | ad hoc |
| GET | /VirtualInventory/VirtualInventory/{VirtualPartUnique} | VirtualInventory | APIResponse + VirtualInventory |
| GET | /VirtualInventory/VirtualInventoryBySupplier/{SupplierUnique}/{SupplierPartNumber} | VirtualInventoryBySupplier | APIResponse + VirtualInventory |
| POST | /VirtualInventory/addVirtualInventory | updateaddVirtualInventory | APIResponse + VirtualInventory |
Note the handshake's operation id in Swagger is Inventory_Handshake, the same as the stocked-inventory class. The path is what disambiguates them.
POST /VirtualInventory/addVirtualInventory
Request shape
{
"ConnectionInfo": { "TerminalNumber": 1 },
"Inventory": [
{
"VirtualPartUnique": 0,
"SupplierUnique": 42,
"SupplierPartNumber": "SUP-991",
"PartNumber": "WIDGET-1",
"ItemNumber": "WID1",
"Description": "Blue widget",
"Category": "01.02",
"BrandName": "Acme",
"Wholesale_Price": 12.50,
"List_Price": 29.99,
"Retail_Price": 24.99,
"Min_Advt_Price": 22.99,
"Weight": 1.2,
"Size_1": "", "Size_2": "", "Size_3": "",
"Barcode_1": "0123456789012",
"CountryCode": "CA",
"Date": "2026-09-01T00:00:00"
}
]
}Note the request array is named Inventory, not VirtualInventory — the response array is named VirtualInventory. They differ.
Match rule — both identifiers are mandatory
Default operation is NoOp; this endpoint refuses rather than guesses.
| Order | Field(s) | Index |
|---|---|---|
| 1 | VirtualPartUnique | key 0 |
| 2 | SupplierUnique and SupplierPartNumber | key 3 |
Missing either half of the pair when there is no unique:
No Supplier Unique provided. Insufficient info to update or insert; Inventory record skipped No Supplier Part Number provided. Insufficient info to update or insert; Inventory record skipped
Because the (supplier, supplier part number) pair is a real natural key, addVirtualInventory is properly idempotent as long as you always send both. This is the cleanest write endpoint in the API for repeated catalogue loads.
Two additional guards, applied before the write
Unlike most domains, virtual inventory validates in AddUpdateRecord as well as in the matcher, and both guards set ActionSuccess: false and downgrade the operation to a no-op:
| Guard | Message |
|---|---|
The SupplierUnique must resolve to a real supplier account | The Supplier does not exist, record not created. |
On update, the incoming ItemNumber and PartNumber must be consistent with the matched record | An error occurred during the update of the record. The Item Number and Part Number of the passed item are not the same. |
The second guard prevents a supplier part number from being silently repointed at a different part. If you genuinely need to repoint it, delete and re-create the catalogue entry in System Five.
ActionResult values
| Value | ActionSuccess | Meaning |
|---|---|---|
Inserted Record | true | Created |
Updated Record | true | Updated |
An error occurred during the insert of the record | false | Write failed |
An error occurred during the update of the record | false | Write failed |
The Supplier does not exist, record not created. | false | Skipped |
... Item Number and Part Number of the passed item are not the same. | false | Skipped |
No Supplier Unique provided. ... | false | Skipped |
No Supplier Part Number provided. ... | false | Skipped |
Unlike stocked inventory, virtual inventory's no-op branch correctly reports ActionSuccess: false. You can trust the flag here.
Query parameters
Fields=... and DetailedResponse=Y behave as described in Common Parameters.
GET /VirtualInventory/VirtualInventory/{VirtualPartUnique}
0 returns everything. Supports Fields, PageSize and PageNumber (both or neither; PageNumber is 1-based).
Supplier catalogues are frequently very large — this is one of the endpoints where an unpaginated fetch will genuinely exhaust memory. Always paginate, and remember the forward-scan cost of deep page numbers described in Customers.
GET /VirtualInventory/VirtualInventoryBySupplier/{SupplierUnique}/{SupplierPartNumber}
Fetches the single catalogue entry for a (supplier, supplier part number) pair, using index 3 directly.
GET /Windward/WebAPI/VirtualInventory/VirtualInventoryBySupplier/42/SUP-991
Both parameters are path segments, in that order, and both are declared as strings in the Swagger despite SupplierUnique being numeric.
Practical notes:
SupplierPartNumbergoes in the URL path, so it must be percent-encoded. Supplier part numbers containing/,#,?or spaces will break the route or be misparsed — encode them, and be aware that an encoded/may still be rejected by the dispatcher. For those parts, fall back to fetching byVirtualPartUnique.- Supports
Fields. - No match returns
IsSuccess: falsewith an empty array, which is the same shape as a failure — see Response Envelopes and Error Semantics.
This is the right endpoint for a "does this supplier part exist in the catalogue?" check before creating a stocked part from it.
GET /VirtualInventory/GetVirtualInventoryRecordCount
{ "SystemFive API Record Count": "VirtualInventory", "Record Count": "204118" }Computed by walking the file. On a large supplier catalogue this is a slow call held under the service's global lock — call it once per sync, not per page.
Field reference
Identity
| Field | Type | Notes |
|---|---|---|
VirtualPartUnique | integer | Match key 1 |
SupplierUnique | integer | Match key 2a. Required. Must resolve to a real supplier |
SupplierPartNumber | string | Match key 2b. Required |
PartUnique | integer | Link to a stocked inventory part, when one exists |
PartNumber, ItemNumber | string | Consistency-checked on update |
Category | string | Ledger-style category number |
Brand, BrandName | integer / string |
Descriptive
Description, Size_1, Size_2, Size_3, Weight, CountryCode, Date
Pricing
| Field | Meaning |
|---|---|
Wholesale_Price | Supplier cost |
List_Price | Supplier list price |
Retail_Price | Suggested retail |
Min_Advt_Price | Minimum advertised price |
Foreign_Price | Foreign-currency price |
Barcodes
Barcode_1, Barcode_2, Barcode_3, Barcode_4 — four fixed slots, not an array. This differs from stocked inventory, where Barcodes is a child collection of unbounded size.


