Source of truth: AO_Base.pas (the mpt* constants), CM_Base.InitGetQueryParameters / InitPostGetQueryParameters / GetFieldListParam / AllowableField, CM_Inventory.InitGetQueryParameters, ServerMethodsBase.GetJSONFields / GetJSONFieldsAndFilters / IsValidOperator.

These parameters recur across many endpoints. Swagger lists them per operation but does not say how they behave, what happens when they are wrong, or how they interact with each other.


1. Query-string parameters

All of these are read from the query string, never from the body, and they are only honoured by endpoints that opt into them. An endpoint that does not declare a parameter will silently ignore it.

ParameterTypeApplies to
Fieldscomma-separated listGET and POST field shaping
DetailedResponseY / anything elsePOST endpoints
PageSizeintegerPaginated GETs
PageNumberintegerPaginated GETs
EffectiveDateTimeISO 8601"changes since" GETs
MarkedDeletedY / anything elseInventory GETs
eCommerceExportY / anything elseInventory GETs
CurrencyCodeintegerInventory GETs
DepartmentintegerInventory GETs
FreeFormNameMapY / anything elseGETs that expose free-form fields
PriceLevelintegerInventory GETs

Note the exact spellings. EffectiveDateTime is not EffectiveDate, and FreeFormNameMap is not FreeFormMap, even though the internal Delphi constants are named mptEffectiveDate and mptFreeFormMap.

A shared failure mode: non-numeric integers throw

CurrencyCode, Department, PriceLevel, PageSize and PageNumber are parsed with StrToInt, not StrToIntDef. Passing a non-numeric value raises a Delphi conversion exception, which surfaces as HTTP 200 with a failure envelope containing text like:

'abc' is not a valid integer value

Send these as bare integers with no units, no thousands separators and no empty string. Omit the parameter entirely rather than sending it empty.


2. Fields — response field shaping

?Fields=PartNumber,Description,OnHand

Parsed by GetFieldListParam into a TStringList via its CommaText property, then applied by CM_Base.AllowableField: a field is emitted only if its name appears in the list.

Rules and gotchas:

  • Comma-separated, in the query string. Because it is parsed as CommaText, values containing spaces or commas must be double-quoted, e.g. Fields="Part Number",Description. Prefer field names without spaces.
  • Matching is by exact name. An unrecognised name is not an error — it simply never matches, so it contributes nothing.
  • If you list only invalid names, you get an empty record object, not an error and not the full record. If every name you send is wrong, each returned record will be {}.
  • Omit Fields to get everything. An absent or empty Fields leaves the list empty and AllowableField returns true for every field.
  • The filter applies to the output only. It does not reduce the work the server does, so it will not make a slow call fast.
  • On POST endpoints, sending Fields has a side effect: see DetailedResponse below.

The separate, body-level Fields array (legacy endpoints only)

Older TServerMethodsWebAPI operations take a different mechanism — a JSON body element, sent with a leading capital at the top level of the request:

{ "Fields": [ "CustomerUnique", "CustomerName", "City" ] }

(The service rewraps it internally as a lowercase fields element before parsing; you always send the capitalised form.)

Handled by ServerMethodsBase.GetJSONFields:

  • Names must match a field's public name exactly (case-sensitive). Public names on this surface differ from the newer classes — CustomerUnique and CustomerName here, versus Unique and Name on the Customer class.
  • Unrecognised names are silently discarded.
  • If no valid field survives, all valid fields are returned. This is the opposite of the query-string Fields behaviour above, which yields an empty object. Be careful not to confuse the two.

3. Filters — body-level filtering (legacy endpoints only)

Legacy *_Get operations accept a Filters array in the body, again with a leading capital at the top level:

{
  "Filters": [
    { "Field": "CustomerName", "Operator": "=",    "Value": "Smith" },
    { "Field": "City",         "Operator": "like", "Value": "Vern"  }
  ]
}

Do not add your own % wildcards to a like value — the server wraps the value in %...% itself. See The Legacy TServerMethodsWebAPI Surface for how the filter becomes SQL, and for the apostrophe caveat that follows from it.

The property names are Field, Operator, Value. (Some in-code comments show FieldName; the parser only recognises Field.)

Valid operators

=, <, >, <=, >=, <>, and like (case-insensitive). Anything else fails the request with:

Error in filter; invalid operator specified: {operator}

Which fields can be filtered

Only fields whose definition declares a filter type other than "none". Each domain publishes its own field list; a field you can read is not necessarily a field you can filter. An unfilterable or unknown field fails the request with:

Error in filter; invalid field specified: {field}

Silent-skip rule

A filter entry is only considered if all three of Field, Operator and Value are non-empty. An entry missing any one of them is skipped without comment. If every entry is skipped, the request fails with:

Filters specified; but no filters found

That message is therefore usually a symptom of a typo in a property name or an empty Value, not of a genuinely empty filter list.

Combination semantics

Filters are ANDed. There is no OR, no grouping and no negation beyond <>. Filtering a value of "" is not possible (it is treated as absent).


4. DetailedResponse — echoing the written record back

?DetailedResponse=Y

Applies to POST endpoints. Comparison is case-insensitive against Y; every other value, including true, 1 and Yes, is treated as no.

When on, each ActionResult entry in the response carries a populated RecordResult object containing the record as it now stands in System Five. When off, RecordResult is null.

Two things worth knowing:

  • Fields implies DetailedResponse. In CM_Base.InitPostGetQueryParameters, if Fields is present and non-empty on a POST, fDetailedPostResponse is forced to true. You can therefore turn on the detailed response simply by asking for fields, and you cannot ask for specific fields while suppressing the detail.
  • The detailed record is produced by re-reading the record after the write, so it reflects everything System Five derived (assigned numbers, computed totals, tax). This is the cheapest reliable way to learn an assigned invoice number or a computed total without a second round trip.

5. Pagination — PageSize and PageNumber

?PageSize=500&PageNumber=1
  • PageNumber is 1-based. Page 0 means "no pagination".
  • PageSize is capped at 50 000 (cMaxPageSize in CM_Base). A larger value is silently reduced to the cap — you are not told. Some Swagger descriptions quote a lower figure (the Invoice endpoints say 2 500); treat those as advisory guidance about memory rather than an enforced limit, and the 50 000 cap as the hard one.
  • PageSize without PageNumber disables pagination. In InitGetQueryParameters, if the endpoint supports PageNumber and the parameter is absent, both PageNumber and PageSize are forced to 0. You must send both or neither.
  • PageNumber is only read at all when PageSize > 0.
  • With pagination off, the endpoint returns everything that matches. On large datasets that means a very large JSON document built entirely in memory, while holding the service's global critical section. Several Swagger descriptions warn about memory errors here; the warning is real.

The count-then-page pattern

For the collections that offer a count operation, the intended sequence is:

  1. Call the *RecordCount / *CountByDate operation for the same criteria.
  2. Compute ceil(count / PageSize).
  3. Loop PageNumber from 1 to that many pages.

The count and the paged fetch are separate, unsynchronised calls. Records written between them shift the pages, so a busy dataset can produce duplicates or gaps at page boundaries. For "changes since" style syncs, prefer paging by an advancing EffectiveDateTime watermark over paging by number.


6. EffectiveDateTime — date and time handling

?EffectiveDateTime=2026-09-01T00:00:00

Parsed with Lib.TryIso8601ToDateTime.

  • ISO 8601, YYYY-MM-DDTHH:MM:SS, with no UTC offset and no Z suffix. The Swagger descriptions state this explicitly and the parser is strict.
  • The value is interpreted in the server's local time zone — the time zone of the machine running the service, not yours and not UTC. Convert before sending, and account for the server's DST transitions.
  • A value that fails to parse produces an immediate failure envelope, for example:
{ "Response": "Failed",
  "Reason": "Invalid Start Date found (01/09/2026); must be in ISO8601 format" }
  • The comparison is inclusive: records dated on or after the value are returned.
  • Date-only fields elsewhere in payloads are also parsed by the ISO 8601 helper (TryGetJSONValue for TPervasiveDate), so use the same format for them. A date that fails to parse becomes the Pervasive zero date rather than an error.

7. Inventory-only query parameters

Honoured by the Inventory class GETs (CM_Inventory.InitGetQueryParameters):

ParameterEffect when set
MarkedDeleted=YInclude parts flagged deleted in System Five. Default is to exclude them.
eCommerceExport=YRestrict results to parts flagged for e-commerce export. Default is no restriction.
CurrencyCode={n}Evaluate prices in this System Five currency. Also sets the underlying record's CurrentCurrency.
Department={n}Evaluate department-specific pricing against this department. Note this affects pricing only — it does not change which department your credentials are authorised for (see Authentication and Authorization).
PriceLevel={n}Return prices at this price level.

MarkedDeleted and eCommerceExport use the same case-insensitive Y test as DetailedResponse; any other value means off.


8. FreeFormNameMap — free-form field headers

?FreeFormNameMap=Y

System Five free-form fields are stored by numeric id, with their human labels held separately as comment records. Records returned by the API carry the ids, not the labels.

Setting FreeFormNameMap=Y adds a FreeFormHeaders array to the response mapping each id to its configured header text, so you can label the values:

"FreeFormHeaders": [
  { "1": "Warranty Period", "FreeFormHeaderIsChanged": false },
  { "2": "Country of Origin", "FreeFormHeaderIsChanged": false }
]

Each entry is an object whose key is the free-form id as a string and whose value is the header text, plus a FreeFormHeaderIsChanged flag (always false on read; it exists for the write direction).

The map is loaded per domain from the comment file at FileNumber + 128 and is the same for every record in the response, so request it once per sync rather than on every page.


9. ConnectionInfo in POST bodies

Covered in Authentication and Authorization section 7. Summary: it is required in practice on the shared-pipeline POST endpoints, only TerminalNumber is read, and omitting it fails the entire request with Connection Info missing before any record is processed. Send:

{ "ConnectionInfo": { "TerminalNumber": 1 } }