URL class: APBill — /Windward/WebAPI/APBill/...
Source of truth: ServerMethodsAPBill.pas, CM_APBill.pas, AO_APBill.pas.
The legacy equivalent is POST /TServerMethodsWebAPI/Insert_AP_Bill — see The Legacy TServerMethodsWebAPI Surface.
Operations
| Verb | Path | Delphi method | Envelope |
|---|---|---|---|
| GET | /APBill/APBill_Handshake | APBill_Handshake | ad hoc |
| GET | /APBill/GetAPBillRecordCount | GetAPBillRecordCount | ad hoc |
| GET | /APBill/APBills/{Unique} | APBills | APIResponse + APBill |
| POST | /APBill/addAPBills | updateaddAPBills | APIResponse + APBill |
POST /APBill/addAPBills
This endpoint behaves differently from every other write endpoint in the API, in three ways that matter a great deal.
1. It is insert-only
// Update is not currently supported. Editing a Bill from an external system // is not something we will likely support. if (aActionParams.Operation = prmInsert) then
Only the insert branch exists. If FindRecordMatch resolves your payload to an existing bill — which it does when you send a Unique that exists — AddUpdateRecord does nothing at all: no write, no error, and ActionSuccess is left at its initialised false with an empty ActionResult.
Never send a
Uniqueon an AP bill. Send0or omit it. AP bills cannot be edited through this API; correct them in System Five.
2. It pre-validates the whole batch, and drops what fails
Unlike the other domains, AP bills are validated before the upsert pipeline runs. Each bill's header is put through five checks in order; a bill only enters the write list if all five pass.
The problem is that the pass/fail flag is a single shared response object reused for every bill in the array:
- Bill 1 fails validation -> not added to the write list, and the failure message is written to
APIResponse.Response. - Bill 2 then passes validation -> the shared flag is reset to success and the failure message from bill 1 is overwritten.
- The batch proceeds, writing bill 2 only. Bill 1 is silently dropped and produces no
ActionResultentry at all.
Submit AP bills one per request. With a single bill in the array the shared-state behaviour is harmless and you get a clear pass/fail. With multiple bills you can lose a bill without any indication in the response. The record array length will be shorter than the array you sent — compare counts if you must batch.
3. It opens a System Five transaction
ProcessHeaderInfo begins a Tranlog transaction (BeginTransaction2) before writing. This is the only write path in the API that does so, and it means the header-plus-lines write for a single bill is atomic in System Five's own transaction log — unlike, say, an invoice.
Request shape
{
"ConnectionInfo": { "TerminalNumber": 1 },
"APBills": [
{
"HeaderInfo": {
"Number": "SUPP-INV-8891",
"Pay": 4471,
"Supplier": 4471,
"Date": "2026-09-01T00:00:00",
"Total": 1250.00,
"Department": 1,
"PO": "PO-2211",
"Description":"September stock",
"DueDate": "2026-10-01T00:00:00"
},
"BillLines": [
{ "LedgerAccount": "5010.000", "Amount": 1000.00, "DbCd": "D",
"Description": "Goods", "Supplier": 4471, "Department": 1 },
{ "LedgerAccountName": "FREIGHT", "Amount": 250.00, "DbCd": "D",
"Description": "Freight", "Supplier": 4471, "Department": 1 }
],
"BillCommentLines": [
{ "CommentLineIndex": 0, "CommentLineData": "Received in full" }
]
}
]
}Required header fields
Assembled dynamically per request:
| Field | Always required | Notes |
|---|---|---|
Number | yes | The supplier's bill/invoice number |
Pay | yes | Payee account unique |
Date | yes | Bill date, ISO 8601 |
Supplier | yes | Supplier account unique |
Department | only when the dataset is departmentalised | Added to the required list when the dataset reports more than zero departments |
A missing required field fails the bill with:
Required parameter(s), Number, Date, not found
(The list is comma-separated with a trailing comma before not found.)
The five validations, in order
Each stops the bill at the first failure.
1. Required parameters
As above.
2. Verify_Bill_Date — date and total
Datemust be present:The required [Date] value was missing from the Parameters!Totalmust be present:The required [Total] value was missing from the Parameters!- The date must be postable — System Five checks it against the open booking period / minimum book month rules. A closed or out-of-range period fails with:
Post Bill Exception: {System Five's message}
This is the check that most often rejects a backdated bill. Bills cannot be posted into a closed accounting period through the API any more than they can in System Five.
3. Verify_Bill_Department
Behaviour depends on whether the dataset is departmentalised:
| Dataset | Rule | Failure message |
|---|---|---|
| Departmentalised | Department must be present | Bill Department Missing. This departmentalized dataset requires a Department value! |
| Departmentalised | must be > 0 | Invalid Bill Department. This departmentalized dataset requires a Department value greater than ZERO! |
| Departmentalised | must not exceed the configured maximum | Invalid Bill Department. Department parameter exceeds maximum department for this dataset. |
| Not departmentalised | must be 0 | Invalid Bill Department. This dataset only accepts ZERO as a department value! |
Note the asymmetry: on a non-departmentalised dataset, sending "Department": 1 is an error. Send 0, or omit it (it is not on the required list in that case).
4. Verify_Bill_Payee — payee and supplier must exist
Paymust be present:The required [Pay] value was missing from the Parameters!Paymust resolve to a real account:Payee Unique not found in dataset [{value}]Suppliermust be present:The required [Supplier] value was missing from the Parameters!Suppliermust resolve:Supplier Unique not found in dataset [{value}]
Pay and Supplier are separate concepts — the supplier who issued the bill and the account the payment is made to. They are usually the same unique, but the API validates them independently and both must be sent.
5. Verify_Bill_Lines — every ledger account must exist
For each entry in BillLines, the ledger account is resolved from one of:
| Field | Resolution |
|---|---|
LedgerAccount | Parsed directly as a System Five ledger number |
LedgerAccountName | Resolved via GetLedgerAccountByConst using the line's Supplier — a named constant such as the supplier's default expense account |
Every resolved account must exist in the chart of accounts. Failures accumulate into one message listing the offending values:
Ledger Account(s) not found in dataset [5010.000,5099.000,] Ledger Account Name(s) not found in dataset [FREIGHT,]
A line with neither field, or one that resolves to a blank ledger number, also fails.
Important: Validate your account mapping once at integration time. The API gives you the full list of unresolvable accounts in a single message, so a dry run against a test dataset is the cheapest way to find mapping gaps.
Write phase
Once a bill passes validation:
- A System Five transaction is opened.
ProcessHeaderInfoinserts the bill header.- If the header succeeded (
headerErrorempty),ProcessLinesposts each line's trandata against the bill. - The bill is updated and the result recorded.
On failure the messages are concatenated with carriage returns:
An error occurred during the insert of the record
{header error}
{line error}RecordDesc is "{Number} - {Description}". RecordId is the bill unique.
Comment lines
BillCommentLines is an array of { "CommentLineIndex": n, "CommentLineData": "..." }, written as comment records against the bill.
Query parameters
| Parameter | Effect |
|---|---|
Fields=... | Shape RecordResult; implies a detailed response |
DetailedResponse=Y | Populate RecordResult |
GET /APBill/APBills/{Unique}
0 returns all bills; a positive value returns one.
| Query parameter | Notes |
|---|---|
Fields | Comma-separated field names |
PageSize / PageNumber | Both or neither; PageNumber is 1-based |
Note the Swagger declares the path parameter as Number (a string) while the Delphi signature takes an integer unique. Send the bill's numeric Unique, not its bill number.
The response array key is APBill. Each record contains HeaderInfo, BillLines, BillCommentLines, and the payee/supplier objects (BillPayeeObject, BillSupplierObject).
GET /APBill/GetAPBillRecordCount
{ "SystemFive API Record Count": "APBills", "Record Count": "3391" }GET /APBill/APBill_Handshake
{ "System Five APBill API": "Handshake", "Version": "1.2.3.4" }Field reference
HeaderInfo
| Field | Type | Required | Notes |
|---|---|---|---|
Number | string | yes | Supplier's bill number |
Pay | integer | yes | Payee account unique; must exist |
Supplier | integer | yes | Supplier account unique; must exist |
Date | ISO 8601 | yes | Must be postable in an open period |
Total | number | yes (checked by Verify_Bill_Date) | |
Department | integer | conditional | Required and > 0 on departmentalised datasets; must be 0 otherwise |
PO | string | no | |
Description | string | no | |
DueDate | ISO 8601 | no | |
Foreign | string | no | |
CurrencyCode | integer | no | |
BookMonth | ISO 8601 | no | |
Job, Expt | no | ||
Unique | integer | do not send | Sending an existing unique makes the bill a silent no-op |
BillLines[]
| Field | Type | Notes |
|---|---|---|
LedgerAccount | string | Ledger number, e.g. 5010.000. Must exist |
LedgerAccountName | string | Alternative to LedgerAccount; resolved against the line's Supplier |
Amount | number | |
DbCd | string | Debit/credit indicator |
Description | string | |
Supplier | integer | Used to resolve LedgerAccountName |
Department | integer | |
CurrencyCode, WholeCurrencyCode, CurrencyDate, DataDate |
BillCommentLines[]
| Field | Type |
|---|---|
CommentLineIndex | integer |
CommentLineData | string |
Read-only in responses
Paid, FullyPaid, FullyPaidDate, Completed, APStage, EnterBy, EnterDate, EnterTime, PaidByDepartment


