Source of truth: s5webapisvc/S5WebAPI.dfm, S5WebAPI.pas, S5WebAPIServerContainer.pas, S5WebAPIService.pas, S5WebAPISvc.ini, OpenAPISpecification.pas.
This page covers what the Swagger document cannot express: how a URL is actually assembled, which host/port answers, and what the HTTP layer does to your response before you see it. Start here — it is the entry point to the rest of the guide.
Quick start
Five calls that take you from "is it running?" to a created invoice. Substitute your own host, port and credentials.
# 1. Is the service up? (no credentials needed, no database touched) curl -u 'HealthCheck:anything' \ 'http://SERVER:215/Windward/WebAPI/HealthCheck/Health_Check' # 2. Are my credentials good and is the dataset live? curl -u 'WEBUSER:secret' \ 'http://SERVER:215/Windward/WebAPI/TServerMethodsWebAPI/Connect' # 3. What departments and tender types does this dataset have? curl -u 'WEBUSER:secret' \ 'http://SERVER:215/Windward/WebAPI/TServerMethodsWebAPI/Get_Departments' curl -u 'WEBUSER:secret' \ 'http://SERVER:215/Windward/WebAPI/TServerMethodsWebAPI/Get_Tender_Types' # 4. Read one invoice curl -u 'WEBUSER:secret' \ 'http://SERVER:215/Windward/WebAPI/Invoice/Invoices/10231' # 5. Create an invoice, asking for the written record back curl -u 'WEBUSER:secret' -H 'Content-Type: application/json' \ -X POST 'http://SERVER:215/Windward/WebAPI/Invoice/addInvoice?DetailedResponse=Y' \ -d @invoice.json
Note steps 1 and 2 are not interchangeable: the health check never opens the dataset, so it keeps answering while the database is unreachable. Only step 2 proves the service can actually reach System Five.
Before writing any code that POSTs, read The Upsert Model — every add* endpoint is an upsert, not an insert.
1. The service
S5WebAPISvc.exe is a Windows service (S5WebAPIService) that embeds an Indy HTTP server and a Delphi DataSnap REST dispatcher. It talks to a System Five Pervasive/Actian Zen dataset as a single logical System Five workstation.
Under the DEBUG build directive the same executable runs as a console application instead of a service, printing its listening ports and waiting on ReadLn. Production builds are services.
Listeners
Three independent listeners are configured from S5WebAPISvc.ini, which lives beside the executable:
| Listener | INI section | INI key | Shipped default | Purpose |
|---|---|---|---|---|
| HTTP | [HTTP] | Port | 215 | The API |
| HTTPS | [HTTPS] | Port | 8081 | The API over TLS |
| Swagger publishing | [Swagger Publishing] | Port | 8000 | Serves static Swagger UI files only |
Notes that matter in practice:
- Do not assume port 8080. The
DSPort = 8080value baked intoS5WebAPI.dfmis the DataSnap TCP/IP port property, not the HTTP listener. The HTTP listener port comes from the INI file and ships as 215. - HTTPS is only started when the certificate settings in
[HTTPS](Cert File,Key File,Root Cert File,Passphrase) resolve to readable files. Otherwise the service starts HTTP-only and logs the reason. There is no HTTP-to-HTTPS redirect, and HTTP is not disabled when HTTPS is enabled. - Swagger publishing is off by default (
Enable Swagger Publishing=N). When enabled, its port serves files out of<service path>\swagger-publishing\as plain static content, handled inTS5WebAPIServer.WebModuleBeforeDispatch. It is a separate port from the API and applies no authentication. - Session timeouts (
Session Timeout, in minutes) are per listener.
Identity of the service against System Five
[System Five Database] in the INI fixes the dataset and the System Five identity that every request runs as:
| Key | Meaning |
|---|---|
Directory | The System Five data directory the service opens |
Serial | System Five serial number |
Department | The department every request is evaluated against (see Authentication) |
Terminal / TerminalName / Identifier | The workstation identity the service registers as |
Department is a service-wide setting, not a per-request one. Every authenticated caller is checked against this single department.
2. URL shape
Two dispatchers are mounted:
| Dispatcher | Path prefix | Notes |
|---|---|---|
DSHTTPWebDispatcher1 | /windward/webapi/... | The documented API surface. DSContext = 'Windward/', RESTContext = 'WebAPI/' |
DSHTTPWebDispatcher2 | /datasnap/... | The stock DataSnap surface, still mounted and still functional |
The canonical form is:
{scheme}://{host}:{port}/Windward/WebAPI/{ClassName}/{MethodName}[/{param}/{param}...]
Path matching is case-insensitive (WebDispatch.PathInfo = 'windward*'), and generated Swagger uses the mixed-case /Windward/WebAPI/ form.
Class names
{ClassName} is the literal Delphi class name resolved in TS5WebAPIServerContainer.DSServerClassGetClass:
{ClassName} in the URL | Delphi unit | Area |
|---|---|---|
TServerMethodsWebAPI | ServerMethodsWebAPI.pas | The large general/legacy surface |
Invoice | ServerMethodsInvoice.pas | Invoices (v2 surface) |
Customer | ServerMethodsCustomer.pas | Customers (v2 surface) |
Inventory | ServerMethodsInventory.pas | Inventory parts (v2 surface) |
VirtualInventory | ServerMethodsVirtualInventory.pas | Virtual / supplier inventory |
Vendors | ServerMethodsVendor.pas | Vendors |
APBill | ServerMethodsAPBill.pas | AP bills |
Category | ServerMethodsCategory.pas | Categories |
Units | ServerMethodsUnit.pas | Units / free-form headers |
Keyword | ServerMethodsKeyword.pas | Keywords |
HealthCheck | ServerMethodsHealthCheck.pas | Liveness |
Note TServerMethodsWebAPI keeps its leading T; the others do not, because those classes are declared without one. Use the name exactly as listed.
Method names and the DataSnap verb prefix
This is the single most common source of confusion, and Swagger hides it.
DataSnap decides which Delphi method to call by prefixing the URL segment with a verb-derived string. The mapping used by this service (OpenAPISpecification.pas, PrepareMethodNameForRestAction) is:
| HTTP verb | Prefix DataSnap prepends |
|---|---|
GET | (none) |
POST | update |
PUT | accept |
DELETE | cancel |
So the Delphi method Invoice.updateaddInvoice is reached as:
POST /Windward/WebAPI/Invoice/addInvoice
and TServerMethodsWebAPI.updateGet_Customers as:
POST /Windward/WebAPI/TServerMethodsWebAPI/Get_Customers
Consequences you need to know:
- A
POSTpath segment never contains the wordupdate. If you copy a Delphi method name out of the source you must strip theupdateprefix. - Several read-only operations are exposed as
POST, notGET, purely because they take a JSON request body (Get_Customers,Get_Parts,Get_Stock,Get_Part_Prices, ...). POSTing to read data is normal here. - No method in this service is registered under the
accept(PUT) orcancel(DELETE) prefixes. There are no PUT or DELETE endpoints. Updates are performed by POSTing to the matchingadd*/*_Updateoperation, and the one delete-style operation isPOST .../Delete_Full_Invoice. - Methods whose declared attribute verb does not match an existing prefix are silently omitted from the generated Swagger, but they may still be callable.
Path parameters
Where an operation takes a path parameter (for example getInvoice), the value is appended as an additional path segment, positionally:
GET /Windward/WebAPI/Invoice/Invoices/12345
DataSnap binds path segments to the Delphi method's parameters in declaration order. Query-string parameters are read separately and are never bound positionally (see Common Parameters).
3. What the HTTP layer does to your response
Three transformations happen after the endpoint returns and before you see the body. All three are invisible in Swagger.
3.1 The status code is forced to 200
TS5WebAPIServerContainer.HTTPServiceTrace sets:
ResponseInfo.ResponseNo := 200;
on every response on the HTTP listener.
Do not branch on the HTTP status code. A validation failure, a missing record, a Pervasive error and a completely successful call all return
200 OK. Success or failure lives in the response body — see Response Envelopes and Error Semantics.
The one documented exception is authentication failure, which is set through DataSnap's invocation metadata before the trace handler runs and does come back as 403 with a body of {"Error":"<reason>"}. See Authentication and Authorization.
3.2 CORS is wide open
Both the HTTP and HTTPS trace handlers add:
Access-Control-Allow-Origin: *
to every response. No other CORS headers are emitted — notably no Access-Control-Allow-Headers and no Access-Control-Allow-Methods — so a browser preflight (OPTIONS) for a request carrying Authorization is not satisfied. Browser-side callers should proxy through their own backend.
3.3 The DataSnap result array is sometimes unwrapped
By default DataSnap wraps a server method's return value in an array under a result key:
{ "result": [ { "...your object..." : "..." } ] }
Endpoints whose Delphi declaration passes true as the second argument to TOpenAPIMethodAttribute are registered in an exclusion list (TOpenAPILib.GetResultExcludedClassMethodNames), and HTTPServiceFormatResult strips the wrapper for them, returning the bare object.
Both shapes exist in this API. Which one you get is a per-endpoint fact, recorded in the per-area pages. Write your client to tolerate both: if the body has exactly one key named result whose value is an array, unwrap it.
4. Concurrency and throughput
- Every server class is registered with
LifeCycle = 'Invocation', so a fresh instance of the server-methods class is created per call. There is no server-side session state you can rely on between calls. - Nearly every endpoint body opens with
CriticalX.Enterand leaves on the way out. The service serialises requests through a single global critical section. Concurrency will not increase throughput, and one slow call (a large unpaginated fetch, for example) blocks every other caller. - Practical consequence: prefer fewer, well-paginated calls; do not fan out parallel requests expecting parallel execution; expect latency under load to be queueing latency rather than per-request cost.
5. Swagger documents
Each server class can emit its own Swagger 2.0 document. The Swagger method writes <ClassName>swagger.json next to the executable and returns the same JSON. basePath in those documents is /Windward/WebAPI/{ClassName}, and each path key is the method name with the DataSnap verb prefix already stripped — which is why the Swagger paths are directly usable while the Delphi method names are not.
Because each class emits a separate document, there is no single Swagger file covering the whole API.
6. Onboarding checklist for a new dataset
Run these once before building against a customer's dataset. Several endpoints behave differently depending on how that dataset is configured, and none of it is discoverable from Swagger — so answering these first saves you debugging behaviour that is actually configuration.
| Question | How to answer it | Why it matters |
|---|---|---|
| Is it departmentalised? | GET /TServerMethodsWebAPI/Get_Departments — a single department numbered 0 means no | Determines the legal Department values on invoices and AP bills |
| Is record state tracking on? | Call any *Changes endpoint | All change-feed endpoints fail without it |
| Which tender types are enabled? | GET /TServerMethodsWebAPI/Get_Tender_Types | Disabled tenders are silently dropped by addInvoice |
| Which price codes exist? | GET /Inventory/GetPriceCodes | RegPriceCode / SalePriceCode take one character from this set |
| What do the size fields mean? | GET /TServerMethodsWebAPI/Get_SizeLabels | Size1/Size2/Size3 are customer-defined |
| What are the free-form ids? | Any GET with ?FreeFormNameMap=Y | Ids are dataset-specific and differ per domain |
| Is multi-currency / departmental inventory configured? | Compare Inventory results with and without CurrencyCode / Department | Those parameters fail silently when the feature is off |
| Can updates change a part's supplier? | Ask the administrator for the Update Inventory Supplier INI setting | When N, supplier changes on addInventory are silently ignored |
| Is the API pointed at the right dataset? | POST /Vendors/ValidateDB | Protects against a service configured against a copy |
| How are business names formatted? | Post a test customer with FirstName/LastName and read RecordDesc | SQ_BusinessNameFormatToFirstLast changes the stored name |


