Three small reference-data domains that share the standard upsert pipeline. Read The Upsert Model first.

Source of truth: ServerMethodsCategory.pas / CM_Category.pas / AO_Category.pas, ServerMethodsUnit.pas / CM_Unit.pas / AO_Unit.pas, ServerMethodsKeyword.pas / CM_Keyword.pas / AO_Keyword.pas.


Categories

URL class: Category — /Windward/WebAPI/Category/...

VerbPathDelphi method
GET/Category/Category_HandshakeCategory_Handshake
GET/Category/Categories/{CategoryNumber}Categories
POST/Category/addCategoriesupdateaddCategories

What a category number is

Categories are identified by a System Five ledger-style number, not an integer id: a dotted numeric string such as 01.02. The API parses it through StringToLed and validates it with LedCheck.

This is the only domain in the API whose primary key is a string path parameter.

GET /Category/Categories/{CategoryNumber}

  • Pass a category number to fetch one category.
  • To fetch all categories, pass the literal string undefined as the path segment (this is what the Swagger description specifies): GET /Windward/WebAPI/Category/Categories/undefined

Supports Fields. It does not support pagination — an all-categories fetch returns everything in one response.

POST /Category/addCategories

{
  "ConnectionInfo": { "TerminalNumber": 1 },
  "Categories": [
    { "CategoryNumber": "01.02", "CategoryName": "Widgets",
      "CategoryType": "I", "Income": "4010.000", "Expense": "5010.000",
      "Income2": "", "Expense2": "" }
  ]
}

Match rule

Only CategoryNumber (key 0). Present and valid -> update if it exists, insert if it does not. Blank or unparseable -> skipped:

Category number missing, record skipped

Because the match is on the number alone, this endpoint is fully idempotent: re-posting the same category number updates in place. There is no way to create a second category with the same number, and no way to change a category's number (that would create a new one).

Fields

FieldTypeNotes
CategoryNumberstringLedger-style, e.g. 01.02. Required
CategoryNamestringUsed at creation
CategoryTypestringCategory type
Income, Income2stringIncome ledger accounts
Expense, Expense2stringExpense ledger accounts

ActionResult is Inserted Record / Updated Record / An error occurred during the insert of the record / An error occurred during the update of the record.

Supports Fields and DetailedResponse=Y.


Units

URL class: Units — /Windward/WebAPI/Units/...

Units are serialised equipment records (vehicles, machines) tracked individually.

VerbPathDelphi method
GET/Units/Unit_HandshakeUnit_Handshake
GET/Units/Units/{UnitId}Units
POST/Units/addUnitupdateaddUnit
POST/Units/UpdateFreeFormHeadersupdateUpdateFreeFormHeaders

As with vendors, the GET path doubles the segment: /Windward/WebAPI/Units/Units/{id}.

GET /Units/Units/{UnitId}

0 returns all units. Supports Fields and FreeFormNameMap=Y. No pagination.

POST /Units/addUnit

{
  "ConnectionInfo": { "TerminalNumber": 1 },
  "Units": [
    { "UnitId": 0, "UnitNo": "TRK-014", "Serial": "1FTFW1E5XKF00001",
      "Make": "Ford", "Model": "F-150", "Year": 2024, "License": "ABC-123",
      "UnitFreeFormGroup": [ { "FreeFormID": 1, "FreeFormData": "Fleet A" } ] }
  ]
}

Match rule — three attempts

OrderFieldIndex
1UnitIdkey 0
2Serialkey 2
3UnitNokey 7

Serial is a match key. Re-posting a unit with a known serial number updates the existing unit rather than creating a duplicate. This is usually what you want for equipment, but it means a mistyped or reused serial silently overwrites another unit's record.

Serial is the recommended idempotency key where serials are genuinely unique.

Fields

FieldTypeNotes
UnitIdintegerMatch key 1
SerialstringMatch key 2
UnitNostringMatch key 3
Make, Model, Licensestring
Yearinteger
UnitFreeFormGrouparrayFree-form values by id

Supports Fields and DetailedResponse=Y.

POST /Units/UpdateFreeFormHeaders

The only endpoint in the API that writes free-form header labels rather than values. Elsewhere free-form headers are read-only (FreeFormNameMap=Y).

{
  "ConnectionInfo": { "TerminalNumber": 1 },
  "UnitFreeFormHeaders": [
    { "1": "Fleet Code",  "FreeFormHeaderIsChanged": true  },
    { "2": "Service Due", "FreeFormHeaderIsChanged": false }
  ]
}

The FreeFormHeaderIsChanged flag is mandatory for a write

Each entry is an object whose key is the free-form id (as a string) and whose value is the new label, plus a FreeFormHeaderIsChanged boolean.

Only entries with FreeFormHeaderIsChanged: true are written. Entries with false, or without the flag, are read and ignored. This lets you round-trip the map you read from FreeFormNameMap=Y and mark only the ones you changed.

Response

An ad hoc APIResponse-style object rather than a record array:

OutcomeIsSuccessResponse
One or more headers writtentrueFree Form Headers Processed Successfully
Payload present but nothing marked changed / no valid entriesfalseNo valid Free Form Header data received
No payloadfalseNo Free Form Header data received

RecordCount is the number of headers actually written.

Free-form headers are dataset-wide configuration, shared by every unit. Changing one relabels the field everywhere.


Keywords

URL class: Keyword — /Windward/WebAPI/Keyword/...

Keywords are the free-text codes attached to invoice lines (job codes, categories of work).

VerbPathDelphi method
GET/Keyword/Keyword_HandshakeKeyword_Handshake
GET/Keyword/Keywords/{KeywordId}Keywords
POST/Keyword/addKeywordupdateaddKeyword

GET /Keyword/Keywords/{KeywordId}

0 returns all keywords. Supports Fields. No pagination.

POST /Keyword/addKeyword

{
  "ConnectionInfo": { "TerminalNumber": 1 },
  "Keywords": [ { "Unique": 0, "Sort": "SVC", "Word": "Service call" } ]
}

Match rule — three attempts

OrderFieldIndex
1Uniquekey 0
2Sortkey 1
3Wordkey 2

Both Sort and Word are match keys, so either can serve as an idempotency key. Equally, changing a keyword's Word while keeping its Sort will update the record matched on Sort — you cannot use this endpoint to create a second keyword sharing a sort code.

Fields

FieldTypeNotes
UniqueintegerMatch key 1
SortstringMatch key 2. Short sort/lookup code
WordstringMatch key 3. The keyword text

Supports Fields and DetailedResponse=Y.

Keyword uniques are what you send as KeywordUnique on an invoice line — see Invoices.