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:

ListenerINI sectionINI keyShipped defaultPurpose
HTTP[HTTP]Port215The API
HTTPS[HTTPS]Port8081The API over TLS
Swagger publishing[Swagger Publishing]Port8000Serves static Swagger UI files only

Notes that matter in practice:

  • Do not assume port 8080. The DSPort = 8080 value baked into S5WebAPI.dfm is 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 in TS5WebAPIServer.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:

KeyMeaning
DirectoryThe System Five data directory the service opens
SerialSystem Five serial number
DepartmentThe department every request is evaluated against (see Authentication)
Terminal / TerminalName / IdentifierThe 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:

DispatcherPath prefixNotes
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 URLDelphi unitArea
TServerMethodsWebAPIServerMethodsWebAPI.pasThe large general/legacy surface
InvoiceServerMethodsInvoice.pasInvoices (v2 surface)
CustomerServerMethodsCustomer.pasCustomers (v2 surface)
InventoryServerMethodsInventory.pasInventory parts (v2 surface)
VirtualInventoryServerMethodsVirtualInventory.pasVirtual / supplier inventory
VendorsServerMethodsVendor.pasVendors
APBillServerMethodsAPBill.pasAP bills
CategoryServerMethodsCategory.pasCategories
UnitsServerMethodsUnit.pasUnits / free-form headers
KeywordServerMethodsKeyword.pasKeywords
HealthCheckServerMethodsHealthCheck.pasLiveness

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 verbPrefix DataSnap prepends
GET(none)
POSTupdate
PUTaccept
DELETEcancel

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 POST path segment never contains the word update. If you copy a Delphi method name out of the source you must strip the update prefix.
  • Several read-only operations are exposed as POST, not GET, 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) or cancel (DELETE) prefixes. There are no PUT or DELETE endpoints. Updates are performed by POSTing to the matching add* / *_Update operation, and the one delete-style operation is POST .../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.Enter and 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.

QuestionHow to answer itWhy it matters
Is it departmentalised?GET /TServerMethodsWebAPI/Get_Departments — a single department numbered 0 means noDetermines the legal Department values on invoices and AP bills
Is record state tracking on?Call any *Changes endpointAll change-feed endpoints fail without it
Which tender types are enabled?GET /TServerMethodsWebAPI/Get_Tender_TypesDisabled tenders are silently dropped by addInvoice
Which price codes exist?GET /Inventory/GetPriceCodesRegPriceCode / SalePriceCode take one character from this set
What do the size fields mean?GET /TServerMethodsWebAPI/Get_SizeLabelsSize1/Size2/Size3 are customer-defined
What are the free-form ids?Any GET with ?FreeFormNameMap=YIds are dataset-specific and differ per domain
Is multi-currency / departmental inventory configured?Compare Inventory results with and without CurrencyCode / DepartmentThose parameters fail silently when the feature is off
Can updates change a part's supplier?Ask the administrator for the Update Inventory Supplier INI settingWhen N, supplier changes on addInventory are silently ignored
Is the API pointed at the right dataset?POST /Vendors/ValidateDBProtects against a service configured against a copy
How are business names formatted?Post a test customer with FirstName/LastName and read RecordDescSQ_BusinessNameFormatToFirstLast changes the stored name