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:

AreaDeprecatedSuccessor
CustomersCustomers_Insert, Customers_Read, Customers_Update, Get_Customers, List_CustomersCustomer class
PartsInsert_Parts, Parts_Read, Parts_Update, Get_Parts, Get_Parts_V2, List_PartsInventory class
Suppliersall five operationsVendors class
InvoicesInvoices_Insert, Invoices_Read, Invoices_Update, Invoices_Get, Insert_Full_InvoiceInvoice class
CategoriesGet_Main_Categories, Get_CategoriesCategory class
AP billsInsert_AP_BillAPBill 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_Suppliers and Invoices_Get are 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

NeedPrefer
Create or update a customer, part, invoice, vendorThe per-domain class (Customer, Inventory, Invoice, Vendors)
Search by arbitrary field/value filtersThis surface — the newer classes only fetch by id. Note every search operation is deprecated, with no successor
Look a customer up by emailThis surface (Get_Customer_By_Email)
Stock levels, kits, superseding parts, part imagesThis surface — no newer equivalent
Contract and master-contract pricingThis surface — no newer equivalent
Rental availability and ratesThis surface — no newer equivalent
Stock adjustmentsThis surface — no newer equivalent
Delete or retype an invoiceThis surface — no newer equivalent
Inventory valuationThis 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:

  • like adds its own wildcards. A value of Bob becomes '%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 use like with 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 Failed response.
  • 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 ContractDate and POExpiryDate are 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 what Department values 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.

ParameterNotes
FileNumberSystem Five file number to query
EffectiveDateISO 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

PathNotes
POST /Get_CustomersFields + Filters search · deprecated
POST /List_CustomersFetch by an array of CustomerUniques · deprecated
POST /Get_Customer_By_EmailLookup by email
POST /Get_Customer_ChangesChanged-since feed
POST /Customers_InsertInsert · deprecated
POST /Customers_ReadRead one · deprecated
POST /Customers_UpdateUpdate · deprecated
POST /Insert_Retail_CustomerInsert, with first/last name handling
POST /Update_Retail_CustomerUpdate, 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 with Customers_Read or GET /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

PathNotes
POST /Get_PartsFields + Filters search, paginated · deprecated
POST /Get_Parts_V2As above, revised model · deprecated
POST /List_PartsFetch by PartUniques or Categories · deprecated
POST /Parts_ReadRead one part · deprecated
POST /Insert_PartsInsert · deprecated
POST /Parts_UpdateUpdate · deprecated
POST /Get_Part_ChangesChanged-since feed
POST /Get_StockStock levels for a list of parts
POST /Get_Stock_ChangesChanged-since stock feed
POST /Get_KitsKit definitions, Fields + Filters
POST /Get_Superseding_PartsSupersession chains
POST /Get_Superseding_Parts_ChangesChanged-since supersession feed
POST /PartImages_FetchFetch images for a part
POST /Add_Part_ImageAttach an image to a part
POST /Perform_Stock_AdjustmentSet a stock level
POST /Get_Inventory_ValueInventory valuation report
GET /Get_SizeLabelsThe 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

ParameterRequiredNotes
PartUniqueyesMust exist: Part Unique not found
ImageURLyesThe service fetches the image from this URL itself
ImageFileNameyesStored 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 D8 for 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

ParameterRequired
PartUniqueyes
StockLevelyes
Departmentyes

Each missing parameter is reported individually:

Required parameter, PartUnique, not found
Required parameter, StockLevel, not found
Required parameter, Department, not found

Important: StockLevel is 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. Send Department 0 on 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:

GroupFields
IdentityIIMainCatNumber, IISubCatNumber, IIPartNumber, IISuppPartNumber
FinalIIFinQuan, IIFinStkVal
OversoldIIOversoldQuan, IIOversoldStkVal
AvailableIIAvaiQuan, IIAvaiStkVal
CommittedIIHoldStkVal, IIHoldWOStkVal, IIOnWOStkVal, IIOnLayStkVal
Special orderIISORecvdStkVal, IISOOnWOStkVal, IISOOnLayStkVal, IISOOnOrdStkVal
On orderIIOnOrdStkVal, 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

PathNotes
POST /Get_Part_PricesPrices for a list of parts at a date
POST /PartPrices_GetFields + Filters search over prices
POST /Get_Part_Price_ChangesChanged-since price feed
POST /Get_Contract_PricesCustomer contract prices
POST /Get_Master_ContractA 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

ParameterRequired
CustomerIDyes
PartUniquesyes (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

ParameterRequired
CustomerUniqueyes

Returns the customer's master contract record: the umbrella agreement that the individual contract prices hang from.


Suppliers

PathNotes
POST /Get_SuppliersFields + Filters search · deprecated
POST /List_SuppliersFetch by SupplierUniques · deprecated
POST /Suppliers_ReadRead one · deprecated
POST /Suppliers_InsertInsert · deprecated
POST /Suppliers_UpdateUpdate · 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

PathNotes
POST /Invoices_GetFields + Filters search · deprecated
POST /Invoices_ReadRead one · deprecated
POST /WebAPI_Invoices_ListFetch by InvoiceUniques
POST /Invoices_InsertInsert (header-level) · deprecated
POST /Invoices_UpdateUpdate · deprecated
POST /Insert_Full_InvoiceInsert a complete invoice · deprecated
POST /Change_Invoice_TypeRetype an invoice
POST /Delete_Full_InvoiceDelete an invoice
POST /InvoiceLines_GetFields + Filters search over lines
POST /InvoiceLines_ReadRead lines
POST /InvoiceLines_InsertAdd lines to an existing invoice
POST /InvoiceLines_UpdateUpdate lines
GET /Get_Tender_TypesValid 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_InvoiceInvoice/addInvoice
Swagger statusdeprecatedcurrent
Envelopelegacy Response/ReasonAPIResponse + Invoice
Reference validationUp-front, explicit, itemisedNone
Tender validationTender types must be enabled and amounts non-emptyTypes checked, plus a total reconciliation for type C
Total vs tender reconciliationNoYes, for type C only
UpsertInsert onlyUpsert — may update an existing invoice
Customer auto-creationNoYes, from InvoiceBilling
Multiple invoices per callYesYes

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_Invoice validates 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 adopt addInvoice instead, 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

DatasetRuleMessage
Departmentalisedmust be non-zeroInvalid Invoice Department. This departmentalized dataset does not support a zero department.
Departmentalisedmust not exceed the maximumInvalid Invoice Department. Department parameter exceeds maximum department for this dataset.
Not departmentalisedmust be zeroInvalid 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).
ValueMeaning
WWork order
EEstimate
CCash / 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

ParameterRequired
InvoiceUniqueyes
InvoiceTypeyes

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

ParameterRequired
InvoiceUniqueyes
InvoiceCustomerUniqueyes
InvoiceTypeyes

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

PathNotes
POST /Get_Rentals_AvailabilityAvailability over a date range
POST /Get_Rental_RatesRate cards for parts

Get_Rentals_Availability

ParameterRequiredNotes
PartsyesArray of part uniques
StartDateyesISO 8601
EndDateyesISO 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

ParameterRequired
PartUniquesyes (array)
Departmentyes
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

PathNotes
POST /Insert_AP_BillInsert 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

PathNotes
GET /Get_Main_CategoriesTop-level categories · deprecated
GET /Get_CategoriesAll 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.