URL class: TServerMethodsWebAPI — /Windward/WebAPI/TServerMethodsWebAPI/...
Source of truth: ServerMethodsWebAPI.pas (~10 400 lines), plus AO_* model units.
This is the original, largest and most heterogeneous part of the API: 60 published operations across nine functional areas. It predates the per-domain classes (Invoice, Customer, Inventory, ...) and uses the legacy Response / Reason envelope described in Response Envelopes and Error Semantics.
Deprecated operations
24 of the 60 operations on this class are flagged deprecated: true in the generated Swagger, and are marked deprecated in the tables below. The flag is set by the TOpenAPIOperationAttribute on each method, so it is what the Swagger UI and any generated client will show.
What the flag does and does not mean:
- It is advisory only. Deprecated operations are still published, still routed and still work exactly as documented. Nothing rejects a call to one.
- No removal date has been announced. Treat deprecation as a signal about direction, not as a countdown.
- Most deprecated operations have a direct successor on a newer per-domain class, which is why they were flagged.
The deprecations fall into a clear pattern — they are the CRUD and search operations that the per-domain classes replaced:
| Area | Deprecated | Successor |
|---|---|---|
| Customers | Customers_Insert, Customers_Read, Customers_Update, Get_Customers, List_Customers | Customer class |
| Parts | Insert_Parts, Parts_Read, Parts_Update, Get_Parts, Get_Parts_V2, List_Parts | Inventory class |
| Suppliers | all five operations | Vendors class |
| Invoices | Invoices_Insert, Invoices_Read, Invoices_Update, Invoices_Get, Insert_Full_Invoice | Invoice class |
| Categories | Get_Main_Categories, Get_Categories | Category class |
| AP bills | Insert_AP_Bill | APBill class |
Everything not in that table is current: the change feeds, stock, kits, supersessions, part images, stock adjustments, valuation, prices and contracts, rentals, invoice-line editing, Change_Invoice_Type, Delete_Full_Invoice, Get_Customer_By_Email, the retail-customer helpers, and the general operations. Those are the capabilities with no newer equivalent, and they are not going anywhere.
Two deprecations have no working successor, and you should read them as warnings rather than instructions.
- Search.
Get_Customers,Get_Parts,Get_SuppliersandInvoices_Getare the only way to query by arbitrary field/value filters. The per-domain classes fetch by id and offer no filtering at all. If you need search, you must use a deprecated operation.Insert_Full_Invoice. Its successor,Invoice/addInvoice, is less strict, not more — see the comparison below before switching.For both, prefer the deprecated operation where it does the job better, and keep the dependency documented on your side so it is easy to find if a successor ever appears.
Choosing between this surface and the newer classes
| Need | Prefer |
|---|---|
| Create or update a customer, part, invoice, vendor | The per-domain class (Customer, Inventory, Invoice, Vendors) |
| Search by arbitrary field/value filters | This surface — the newer classes only fetch by id. Note every search operation is deprecated, with no successor |
| Look a customer up by email | This surface (Get_Customer_By_Email) |
| Stock levels, kits, superseding parts, part images | This surface — no newer equivalent |
| Contract and master-contract pricing | This surface — no newer equivalent |
| Rental availability and rates | This surface — no newer equivalent |
| Stock adjustments | This surface — no newer equivalent |
| Delete or retype an invoice | This surface — no newer equivalent |
| Inventory valuation | This surface — no newer equivalent |
In short: the newer classes are better for writes and single-record reads; this surface is the only place many capabilities exist at all.
Conventions across this surface
Every operation on this class follows the same shape unless noted.
Verb and path
Almost everything is POST, even the read operations, because the parameters travel as a JSON body. Strip the Delphi update prefix to get the path — see Transport, URLs and Hosting.
The Response / Reason envelope
{ "Response": "Success", "Reason": "3 customers found", "Results": { }, "Stopwatch": "1" }
Success is Response == "Success". Failures use "Failed" here (not "Failure"), so test for success rather than matching a failure spelling.
Two gates run before any operation does work
1. VerifyFundamentalConnections — checks that the Pervasive/Zen engine is healthy and that a System Five session could be obtained:
{ "Response": "Failed", "Reason": "The Pervasive database license check has failed; ..." }
{ "Response": "Failed", "Reason": "Unable to retrieve valid session" }
These are infrastructure failures, not payload failures. Retrying with a different payload will not help.
2. Verify_Required_Params — checks a per-operation list of required body keys:
{ "Response": "Failed", "Reason": "Required parameter(s),InvoiceUnique,InvoiceType,not found" }
Note the message has no spaces after the commas and a trailing comma before not found. It lists all missing parameters at once.
Also note the check is for presence, not validity. A required key present with an empty string or null passes this gate and fails later, usually with a less helpful message.
Fields and Filters in the body
The search-style operations (Get_Customers, Get_Parts, Get_Suppliers, Get_Kits, PartPrices_Get, Invoices_Get, InvoiceLines_Get, Bill_Get) take:
{
"Fields": [ "CustomerUnique", "CustomerName", "City" ],
"Filters": [ { "Field": "City", "Operator": "=", "Value": "Vernon" } ],
"PageSize": 500,
"PageNumber": 1
}
Note the capitalisation: Fields and Filters with a leading capital at the top level of the request. (Internally they are rewrapped as lowercase fields / filters; you send the capitalised form.)
Semantics are covered in Common Parameters. The parts that matter here:
- Omitting
Fields, or listing only unknown names, returns all fields. - Filters are ANDed; there is no
OR. - Operators:
=,<,>,<=,>=,<>,like.
How filtering actually works — and its consequences
Unlike the newer classes, which walk Btrieve indexes, these operations build a SQL WHERE clause and run it against the Pervasive SQL layer, then render the result through System Five's XML export:
select * from Account WHERE (Account.City='Vernon') AND (Account.State='BC');
Practical consequences:
likeadds its own wildcards. A value ofBobbecomes'%Bob%'. Do not add%yourself — you will get%%Bob%%, which still works but is not what you meant, and a leading%you add cannot be removed.- Text values are wrapped in single quotes with no escaping. A value containing an apostrophe (
O'Brien) produces malformed SQL and the call fails. There is no way to escape it through the API — filter on a different field, or uselikewith a fragment that avoids the quote. - Numeric fields are emitted unquoted; sending a non-numeric value for a numeric field produces a SQL error surfaced as a
Failedresponse. - Phone fields are matched with the punctuation stripped from the stored value, so you can filter on digits alone.
- Only fields whose definition declares a filter type can be filtered. Date fields such as
ContractDateandPOExpiryDateare defined as unfilterable — you cannot filter customers by date on this surface.
General
GET /TServerMethodsWebAPI/Handshake
{ "System Five Web API": "Handshake", "Version": "1.2.3.4" }
Does not touch the database. Confirms credentials and reports the build.
GET /TServerMethodsWebAPI/Connect
The database-touching counterpart: establishes a System Five session and reports whether it succeeded. This is the correct readiness probe for an integration — unlike HealthCheck/Health_Check, which authenticates nobody and never opens the dataset.
GET /TServerMethodsWebAPI/Get_Departments
{ "Response": "Success",
"Results": { "Departments": [ { "DepartmentNumber": "1", "DepartmentName": "Main" } ] } }
DepartmentNumber is a string.
Important: On a non-departmentalised dataset the loop produces nothing, so a single synthetic entry for department zero is added. If you get exactly one department numbered
0, the dataset is not departmentalised — which in turn determines whatDepartmentvalues the AP bill and invoice endpoints will accept (see AP Bills).
GET /TServerMethodsWebAPI/Get_Salespeople
A list of valid salespeople with UniqueNumber and name. The uniques are what you send as InvoiceSalesman on an invoice header.
POST /TServerMethodsWebAPI/RecState_FetchChanges
The raw record-state change feed, for any System Five file.
| Parameter | Notes |
|---|---|
FileNumber | System Five file number to query |
EffectiveDate | ISO 8601 |
Requires record state tracking to be enabled — see Customers.
Returns the changed record ids for that file. Use it when you need change tracking for a file that has no dedicated *Changes endpoint; you then read each record through whatever endpoint serves it.
Customers
| Path | Notes |
|---|---|
POST /Get_Customers | Fields + Filters search · deprecated |
POST /List_Customers | Fetch by an array of CustomerUniques · deprecated |
POST /Get_Customer_By_Email | Lookup by email |
POST /Get_Customer_Changes | Changed-since feed |
POST /Customers_Insert | Insert · deprecated |
POST /Customers_Read | Read one · deprecated |
POST /Customers_Update | Update · deprecated |
POST /Insert_Retail_Customer | Insert, with first/last name handling |
POST /Update_Retail_Customer | Update, with first/last name handling |
Get_Customer_By_Email
{ "EmailAddress": "jo@example.com" }
This does not filter the customer file. It looks the address up in the comment file (File_Account + 128, key 2 = file number + comment), then reads the account that comment belongs to.
That has real consequences:
- The match is on the whole comment value — an exact match on the stored email string. No partial matching, no case-insensitivity guarantee.
- It returns at most one customer, the first comment record that matches. A shared address across several accounts returns only one of them.
- The returned object is a basic customer summary (
CreateBasicCustomerJson), not the full record. Follow up withCustomers_ReadorGET /Customer/Customers/{id}if you need everything.
Failure messages:
Unable to locate customer record for {email}; status {n}
which covers both "no comment found" and "comment found but the account is missing" — you cannot distinguish them from the message.
Insert_Retail_Customer / Update_Retail_Customer
Specialised variants that accept FirstName and LastName and compose the System Five business name from them, using the same SQ_BusinessNameFormatToFirstLast setup question described in Customers. Use these rather than Customers_Insert when you are dealing with individuals rather than businesses.
List_Customers
{ "CustomerUniques": [ "8842", "8843" ] }
The uniques are supplied as strings in the model.
Inventory / parts
| Path | Notes |
|---|---|
POST /Get_Parts | Fields + Filters search, paginated · deprecated |
POST /Get_Parts_V2 | As above, revised model · deprecated |
POST /List_Parts | Fetch by PartUniques or Categories · deprecated |
POST /Parts_Read | Read one part · deprecated |
POST /Insert_Parts | Insert · deprecated |
POST /Parts_Update | Update · deprecated |
POST /Get_Part_Changes | Changed-since feed |
POST /Get_Stock | Stock levels for a list of parts |
POST /Get_Stock_Changes | Changed-since stock feed |
POST /Get_Kits | Kit definitions, Fields + Filters |
POST /Get_Superseding_Parts | Supersession chains |
POST /Get_Superseding_Parts_Changes | Changed-since supersession feed |
POST /PartImages_Fetch | Fetch images for a part |
POST /Add_Part_Image | Attach an image to a part |
POST /Perform_Stock_Adjustment | Set a stock level |
POST /Get_Inventory_Value | Inventory valuation report |
GET /Get_SizeLabels | The dataset's size-field labels |
List_Parts
{ "PartUniques": [ 1, 2, 3 ] }
or
{ "Categories": [ "01.02", "01.03" ] }
Exactly one of the two is required; omitting both fails with:
You must provide either PartUniques or Categories parameter
Fetching by Categories is the efficient way to pull a catalogue slice — far cheaper than paging Get_Parts with a category filter.
Get_Parts vs Get_Parts_V2
Both take the same Fields / Filters / PageSize / PageNumber request. V2 returns a revised response model. PageSize and PageNumber must both be present or pagination is skipped entirely — the code only reads them when both keys exist, and parses them with StrToInt, so a non-numeric value raises a conversion error rather than defaulting.
Get_Stock
{ "Parts": [ 4471, 4472 ] }
Returns current stock figures for the listed parts. This is the operation to use for an availability check before creating an invoice — remember that addInvoice runs with oversell warnings suppressed and will happily take a line you cannot fill (see Invoices).
Add_Part_Image
| Parameter | Required | Notes |
|---|---|---|
PartUnique | yes | Must exist: Part Unique not found |
ImageURL | yes | The service fetches the image from this URL itself |
ImageFileName | yes | Stored filename |
Rules:
- The server downloads the image. You supply a URL, not the bytes. The URL must be reachable from the machine running the service — not from your client. Failure gives
Unable to retrieve image from given URL. - Only JPEG and PNG are accepted, detected by inspecting the file's magic bytes (
FF D8for JPEG; the 8-byte PNG signature) and then actually loading the image. The file extension and the URL are irrelevant; a mislabelled or corrupt file is rejected. - Maximum size is 1 MB (1 000 000 bytes). Larger gives
Image exceeds max size. Max size is 1 MB. - Images are indexed per part; the service finds existing images and either updates a matching one or appends at the next index.
PartImages_Fetch returns images base64-encoded. Images above the same 1 MB threshold are not returned; the field instead carries the literal string Image Too Large. Must not be larger than 1MB. Check for that string before attempting a base64 decode.
Perform_Stock_Adjustment
| Parameter | Required |
|---|---|
PartUnique | yes |
StockLevel | yes |
Department | yes |
Each missing parameter is reported individually:
Required parameter, PartUnique, not found Required parameter, StockLevel, not found Required parameter, Department, not found
Important:
StockLevelis the resulting level, not a delta. This operation sets stock to the value you supply for that part in that department; it does not add to or subtract from the current figure. It creates a stock adjustment transaction in System Five, with the accounting consequences that implies. SendDepartment0on non-departmentalised datasets.
This is a genuinely destructive operation with financial impact — treat it with the same care as posting a document.
Get_Inventory_Value
An inventory valuation report. The response carries a wide set of valuation columns, all prefixed II:
| Group | Fields |
|---|---|
| Identity | IIMainCatNumber, IISubCatNumber, IIPartNumber, IISuppPartNumber |
| Final | IIFinQuan, IIFinStkVal |
| Oversold | IIOversoldQuan, IIOversoldStkVal |
| Available | IIAvaiQuan, IIAvaiStkVal |
| Committed | IIHoldStkVal, IIHoldWOStkVal, IIOnWOStkVal, IIOnLayStkVal |
| Special order | IISORecvdStkVal, IISOOnWOStkVal, IISOOnLayStkVal, IISOOnOrdStkVal |
| On order | IIOnOrdStkVal, IIOnBkOrdStkVal |
Quantities (*Quan) and values (*StkVal) are reported separately. This is a report-shaped endpoint: expect it to be slow, and it runs under the service's global lock.
GET /Get_SizeLabels
Returns the labels configured for the three size fields (Size1, Size2, Size3) on inventory records. Call it once to know what those fields mean in a given dataset — they are customer-configurable and mean nothing on their own.
Prices
| Path | Notes |
|---|---|
POST /Get_Part_Prices | Prices for a list of parts at a date |
POST /PartPrices_Get | Fields + Filters search over prices |
POST /Get_Part_Price_Changes | Changed-since price feed |
POST /Get_Contract_Prices | Customer contract prices |
POST /Get_Master_Contract | A customer's master contract |
The 100-price-schedule ceiling
Get_Part_Prices and Get_Part_Price_Changes are documented as max. 100 price schedules. If the dataset has more than 100 schedules configured, these operations return the first 100 only, silently. Where a customer runs many schedules, read prices from the Inventory class instead, using the PriceLevel query parameter to select the ones you need.
Get_Contract_Prices
| Parameter | Required |
|---|---|
CustomerID | yes |
PartUniques | yes (array) |
Returns contract pricing for that specific customer on those parts. Contract prices override normal schedules in System Five, so if you are quoting to a contract customer this — not Get_Part_Prices — is the price to show.
Get_Master_Contract
| Parameter | Required |
|---|---|
CustomerUnique | yes |
Returns the customer's master contract record: the umbrella agreement that the individual contract prices hang from.
Suppliers
| Path | Notes |
|---|---|
POST /Get_Suppliers | Fields + Filters search · deprecated |
POST /List_Suppliers | Fetch by SupplierUniques · deprecated |
POST /Suppliers_Read | Read one · deprecated |
POST /Suppliers_Insert | Insert · deprecated |
POST /Suppliers_Update | Update · deprecated |
These act on the same records as the Vendors class (Vendors). "Supplier" and "vendor" are the same System Five account type (P). Use this surface for searching; use Vendors for writes, where the upsert semantics are documented.
Note that every operation in this section is deprecated, including the search. Vendors covers the reads and writes by id, but nothing replaces Get_Suppliers for filtering, so a supplier search still has to go through a deprecated operation.
Invoices
| Path | Notes |
|---|---|
POST /Invoices_Get | Fields + Filters search · deprecated |
POST /Invoices_Read | Read one · deprecated |
POST /WebAPI_Invoices_List | Fetch by InvoiceUniques |
POST /Invoices_Insert | Insert (header-level) · deprecated |
POST /Invoices_Update | Update · deprecated |
POST /Insert_Full_Invoice | Insert a complete invoice · deprecated |
POST /Change_Invoice_Type | Retype an invoice |
POST /Delete_Full_Invoice | Delete an invoice |
POST /InvoiceLines_Get | Fields + Filters search over lines |
POST /InvoiceLines_Read | Read lines |
POST /InvoiceLines_Insert | Add lines to an existing invoice |
POST /InvoiceLines_Update | Update lines |
GET /Get_Tender_Types | Valid tender types |
Note the path /WebAPI_Invoices_List really does carry the WebAPI_ prefix — the Delphi method is updateWebAPI_Invoices_List, so only the update is stripped. Its Swagger operation id is ListInvoices, which does not match the path.
Insert_Full_Invoice vs Invoice/addInvoice
Both create a complete invoice. The differences that matter:
Insert_Full_Invoice | Invoice/addInvoice | |
|---|---|---|
| Swagger status | deprecated | current |
| Envelope | legacy Response/Reason | APIResponse + Invoice |
| Reference validation | Up-front, explicit, itemised | None |
| Tender validation | Tender types must be enabled and amounts non-empty | Types checked, plus a total reconciliation for type C |
| Total vs tender reconciliation | No | Yes, for type C only |
| Upsert | Insert only | Upsert — may update an existing invoice |
| Customer auto-creation | No | Yes, from InvoiceBilling |
| Multiple invoices per call | Yes | Yes |
The Swagger description for Insert_Full_Invoice says "This call does not validate the tender that is being passed to it." That is true only of the totals: Verify_Invoice_Tenders does run, and does reject a tender whose type is disabled or whose amount is empty. What it does not do is check that the tenders add up to the invoice total, which addInvoice does for type C.
Insert_Full_Invoicevalidates references far more thoroughly, and it is deprecated anyway. If you want a create that fails loudly on a bad customer, part or department rather than one that silently creates a customer account or updates an existing invoice, it is still the better operation — the deprecation flag does not change that, and its successor is the looser of the two. Weigh the stricter validation against the legacy envelope and the flag, and if you do adoptaddInvoiceinstead, do your reference checking client-side first.
Required parameters
InvoiceDate, InvoiceType, InvoiceBookMonth, InvoiceCustomer, InvoiceDepartment
The six validations, in order
1. Verify_Required_Params — as above.
2. Verify_Invoice_Department
| Dataset | Rule | Message |
|---|---|---|
| Departmentalised | must be non-zero | Invalid Invoice Department. This departmentalized dataset does not support a zero department. |
| Departmentalised | must not exceed the maximum | Invalid Invoice Department. Department parameter exceeds maximum department for this dataset. |
| Not departmentalised | must be zero | Invalid Invoice Department. This non-departmentalized dataset does not support non-zero departments. |
3. Verify_Invoice_Type — only three types are accepted:
Invalid Invoice Type. Valid types are W(Work Order),E(Estimate),C(Cash).
| Value | Meaning |
|---|---|
W | Work order |
E | Estimate |
C | Cash / completed sale |
Note Invoice/addInvoice does not enforce this list — it accepts any first character and only applies the total/tender check to C. This operation is stricter.
4. Verify_Invoice_Customer — InvoiceCustomer must resolve:
Customer Unique not found in dataset [8842]
There is no auto-creation here. Resolve or create the customer first.
5. Verify_Invoice_Lines — every line's part must exist. Failures are accumulated into one message listing all offenders:
Part Unique(s) not found in dataset [4471,4499,]
6. Verify_Invoice_Tenders — each tender's type must be valid, reported as type/value pairs:
Invoice Tender(s) not valid [X:100.00,]
Call GET /Get_Tender_Types first to learn which types the dataset enables.
Only if all six pass is the invoice written.
Change_Invoice_Type
| Parameter | Required |
|---|---|
InvoiceUnique | yes |
InvoiceType | yes |
Converts an existing invoice from one type to another — for example an estimate (E) into a cash sale (C), which is how a quote becomes an order.
Failure cases:
Could not find Invoice, please check the InvoiceUnique parameter.
Found Invoice but Invoice is not editable.
Unable to change to Invoice Type {type}
"Not editable" means System Five's own rules block the change — typically the invoice is posted, paid, or in a closed period. The API cannot override that.
Returns the details of the changed invoice.
Delete_Full_Invoice
| Parameter | Required |
|---|---|
InvoiceUnique | yes |
InvoiceCustomerUnique | yes |
InvoiceType | yes |
All three must match the invoice on file. The customer unique and type are not used to locate the invoice — they are confirmation checks, deliberately requiring the caller to prove it knows what it is deleting:
Could not find Invoice, please check the InvoiceUnique parameter. Found Invoice but InvoiceCustomerUnique did not match. Found Invoice but InvoiceType did not match.
This is the only delete operation in the entire API, and it is irreversible. Read the invoice first (GET /Invoice/Invoices/{unique}) and echo the values back rather than assuming them.
InvoiceLines_Insert / InvoiceLines_Update
Operate on lines of an existing invoice, which Invoice/addInvoice cannot do without re-posting the whole document (and, on its update path, appending duplicate lines — see Invoices). Use these to amend an invoice's lines in place.
GET /Get_Tender_Types
Returns the tender types enabled for the dataset. Essential before posting tenders through either invoice endpoint: disabled tender types are silently dropped by addInvoice and rejected by Insert_Full_Invoice.
Rentals
| Path | Notes |
|---|---|
POST /Get_Rentals_Availability | Availability over a date range |
POST /Get_Rental_Rates | Rate cards for parts |
Get_Rentals_Availability
| Parameter | Required | Notes |
|---|---|---|
Parts | yes | Array of part uniques |
StartDate | yes | ISO 8601 |
EndDate | yes | ISO 8601 |
Both dates are parsed with the same ISO 8601 helper as everywhere else, and both report the same message on failure:
Invalid EffectiveDate found ({value}); must be in ISO8601 format
— note it says EffectiveDate regardless of which of the two dates was bad, so validate both before sending.
Returns availability for each part across the requested window, accounting for existing rental bookings.
Get_Rental_Rates
| Parameter | Required |
|---|---|
PartUniques | yes (array) |
Department | yes |
Parameters not properly formatted. JSON Array required.
means PartUniques was not sent as a JSON array — a single value must still be wrapped, e.g. [4471].
AP bills
| Path | Notes |
|---|---|
POST /Insert_AP_Bill | Insert a bill · deprecated |
The legacy equivalent of POST /APBill/addAPBills. The newer class validates more thoroughly and is documented in AP Bills; prefer it, and read the batching caveat there before sending more than one bill.
Bill_Get and Bill_Read exist as internal methods but are not exposed with OpenAPI attributes, so they do not appear in the generated Swagger. They may still be callable at /Bill_Get and /Bill_Read; treat them as unsupported.
Categories
| Path | Notes |
|---|---|
GET /Get_Main_Categories | Top-level categories · deprecated |
GET /Get_Categories | All categories · deprecated |
Read-only. Writes go through the Category class (Categories, Units and Keywords). Get_Main_Categories returns only the top level of the ledger-number hierarchy, which is the cheaper call when you are building a navigation tree.


