{"openapi":"3.1.0","info":{"title":"OPDC API","version":"3.0.0","contact":{"name":"Twinn Engineering","url":"https://twinn.engineering"},"description":"The OPDC API, version 3.\n\n**This specification is the contract.** The server, the generated clients and\nthis reference are all produced from it, so what is written here is what the\nAPI does.\n\nv3 ships **beside** v2, not instead of it. `/api/v2` remains available as a\nfrozen compatibility surface and is never deleted; no v2 integration has to\nmove to keep working."},"tags":[{"name":"Ledger","description":"The general ledger: the chart of accounts, balances by period and sub-account, fiscal setup, and the journal-posting endpoint every other module's accounting flows through."},{"name":"Accounts payable","description":"Vendor invoices and the payments that settle them. Posting an invoice moves the AP control account and, where the lines carry a job, writes job cost."},{"name":"Accounts receivable","description":"Customer invoices and receipts, including the receivable side of progress billing."},{"name":"Purchasing","description":"Purchase orders and receipts. Creating an order posts **no** general-ledger entry: an order is a commitment, and the accounting happens when the receipt is posted."},{"name":"Inventory","description":"Parts, assemblies, stock by location, movement history and allocations."},{"name":"Jobs and costing","description":"Jobs, job costs, phases, cost codes and the commitments recorded against a job."},{"name":"Project documents","description":"The forecasting and billing documents that hang off a job: cost-, hours- and units-to-complete, AIA progress billing, and proposals. All parent-and-lines shapes."},{"name":"Payroll","description":"Sage 100 Contractor payroll (menu 5): employees, paychecks, timecards, calculations and paygroups, plus the endpoint that posts a payroll batch to the ledger. **`Employee` is a PII surface**: pay rates, salary, birth date and contact details are readable by any key that can reach the read surface."},{"name":"Service management","description":"Service contracts, scheduled work, service locations and equipment, and the work-order endpoint that posts a service invoice with its inventory and receivable."},{"name":"Equipment","description":"The equipment master and the costs and revenues charged against it."},{"name":"Vendors and clients","description":"The vendor master, the client master, and the satellite records hanging off each: contacts, certificates, balances and negotiated part prices."},{"name":"Banking and tax","description":"Bank and credit-card reconciliation, banking transactions, card issuers, and the tax districts and authorities a service invoice is taxed under."},{"name":"Utility","description":"Operational endpoints: check the on-premise agent is reachable, and take or release the maintenance lock on the Sage database. **These three answer with a different error shape to the rest of the API.** See the note on each operation."}],"paths":{"/ap-invoices":{"post":{"operationId":"ApInvoices_post","summary":"Insert an AP invoice","description":"Insert an AP invoice, cascading to a GL journal and job costs when it posts.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.idempotencyKey"},{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApInvoiceWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Accounts payable"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApInvoiceInput"}}}}}},"/ap-invoices/query":{"post":{"operationId":"ApInvoices_query","summary":"Query AP invoices","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePayableInvoice"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Accounts payable"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestPayableInvoice"}}}}}},"/ap-payments/query":{"post":{"operationId":"ApPayments_query","summary":"Query AP payments","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePayablePayment"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Accounts payable"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ar-invoices/query":{"post":{"operationId":"ArInvoices_query","summary":"Query AR invoices","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseReceivableInvoice"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Accounts receivable"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestReceivableInvoice"}}}}}},"/ar-payments/query":{"post":{"operationId":"ArPayments_query","summary":"Query AR payments","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseReceivablePayment"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Accounts receivable"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/assemblies/query":{"post":{"operationId":"Assemblies_query","summary":"Query Assemblies","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseAssembly"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestAssembly"}}}}}},"/assembly-classes/query":{"post":{"operationId":"AssemblyClasses_query","summary":"Query Assembly classes","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseAssemblyClass"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/bank-reconciliations/query":{"post":{"operationId":"BankReconciliations_query","summary":"Query Bank reconciliations","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseBankReconciliation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Banking and tax"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBankReconciliation"}}}}}},"/banking-transactions/query":{"post":{"operationId":"BankingTransactions_query","summary":"Query Banking transactions","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseBankingTransaction"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Banking and tax"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/benefits/query":{"post":{"operationId":"Benefits_query","summary":"Query Benefits","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseBenefit"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/budgets/query":{"post":{"operationId":"Budgets_query","summary":"Query Budgets","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseBudget"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBudget"}}}}}},"/card-issuers/query":{"post":{"operationId":"CardIssuers_query","summary":"Query Card issuers","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseCardIssuer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Banking and tax"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/change-orders/query":{"post":{"operationId":"ChangeOrders_query","summary":"Query Change orders","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseChangeOrder"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestChangeOrder"}}}}}},"/clients/match":{"post":{"operationId":"Clients_match","summary":"Match Clients","description":"Fuzzy-match this entity by Levenshtein distance.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponseClient"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}}}}},"/clients/query":{"post":{"operationId":"Clients_query","summary":"Query Clients","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseClient"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/cost-codes/query":{"post":{"operationId":"CostCodes_query","summary":"Query Cost codes","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseCostCode"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/cost-divisions/query":{"post":{"operationId":"CostDivisions_query","summary":"Query Cost divisions","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseCostDivision"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/cost-to-completes/query":{"post":{"operationId":"CostToCompletes_query","summary":"Query Cost to completes","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseCostToComplete"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Project documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestCostToComplete"}}}}}},"/cost-types/query":{"post":{"operationId":"CostTypes_query","summary":"Query Cost types","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseCostType"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/credit-card-reconciliations/query":{"post":{"operationId":"CreditCardReconciliations_query","summary":"Query Credit card reconciliations","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseCreditCardReconciliation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Banking and tax"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestCreditCardReconciliation"}}}}}},"/daily-payrolls/query":{"post":{"operationId":"DailyPayrolls_query","summary":"Query Daily payrolls","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseDailyPayroll"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/employee-earnings/query":{"post":{"operationId":"EmployeeEarningsRead_query","summary":"Query Employee earnings","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseEmployeeEarnings"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/employees/match":{"post":{"operationId":"Employees_match","summary":"Match Employees","description":"Fuzzy-match this entity by Levenshtein distance.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponseEmployee"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}}}}},"/employees/query":{"post":{"operationId":"Employees_query","summary":"Query Employees","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseEmployee"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/equipment-costs/query":{"post":{"operationId":"EquipmentCosts_query","summary":"Query Equipment costs","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseEquipmentCost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Equipment"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/equipment-revenues/query":{"post":{"operationId":"EquipmentRevenues_query","summary":"Query Equipment revenues","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseEquipmentRevenue"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Equipment"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/equipment-types/query":{"post":{"operationId":"EquipmentTypes_query","summary":"Query Equipment types","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseEquipmentType"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Equipment"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/equipment/query":{"post":{"operationId":"EquipmentRead_query","summary":"Query Equipment","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseEquipment"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Equipment"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/historical-average-costs/query":{"post":{"operationId":"HistoricalAverageCosts_query","summary":"Query Historical average costs","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseHistoricalAverageCost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/hours-to-completes/query":{"post":{"operationId":"HoursToCompletes_query","summary":"Query Hours to completes","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseHoursToComplete"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Project documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestHoursToComplete"}}}}}},"/inventory-allocations":{"post":{"operationId":"InventoryAllocations_post","summary":"Record an inventory allocation","description":"Record an allocation, moving stock and writing a job cost per status and purpose.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.idempotencyKey"},{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryAllocationWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryAllocationInput"}}}}}},"/inventory-allocations/query":{"post":{"operationId":"InventoryAllocations_query","summary":"Query Inventory allocations","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventoryAllocation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestInventoryAllocation"}}}}}},"/inventory-balances/query":{"post":{"operationId":"InventoryBalances_query","summary":"Query Inventory balances","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventoryBalance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/inventory-consumed/query":{"post":{"operationId":"InventoryConsumedRead_query","summary":"Query Inventory consumed","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventoryConsumed"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/inventory-histories/query":{"post":{"operationId":"InventoryHistories_query","summary":"Query Inventory histories","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventoryHistory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/inventory-locations/query":{"post":{"operationId":"InventoryLocations_query","summary":"Query Inventory locations","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventoryLocation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/inventory-quantities/query":{"post":{"operationId":"InventoryQuantities_query","summary":"Query Inventory quantities","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventoryQuantity"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/inventory-serials/query":{"post":{"operationId":"InventorySerials_query","summary":"Query Inventory serials","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseInventorySerial"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/job-costs/query":{"post":{"operationId":"JobCosts_query","summary":"Query Job costs","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseJobCost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/job-paygroups/query":{"post":{"operationId":"JobPaygroups_query","summary":"Query Job paygroups","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseJobPaygroup"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/job-phases/query":{"post":{"operationId":"JobPhases_query","summary":"Query Job phases","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseJobPhase"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/job-types/query":{"post":{"operationId":"JobTypes_query","summary":"Query Job types","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseJobType"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/jobs/match":{"post":{"operationId":"Jobs_match","summary":"Match Jobs","description":"Fuzzy-match this entity by Levenshtein distance.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponseJob"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}}}}},"/jobs/query":{"post":{"operationId":"Jobs_query","summary":"Query Jobs","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseJob"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ledger-accounts/query":{"post":{"operationId":"LedgerAccounts_query","summary":"Query Ledger accounts","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseLedgerAccount"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ledger-balances/query":{"post":{"operationId":"LedgerBalances_query","summary":"Query Ledger balances","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseLedgerBalance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ledger-setups/query":{"post":{"operationId":"LedgerSetups_query","summary":"Query Ledger setups","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseLedgerSetup"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ledger-sub-balances/query":{"post":{"operationId":"LedgerSubBalances_query","summary":"Query Ledger sub balances","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseLedgerSubBalance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ledger-subaccounts/query":{"post":{"operationId":"LedgerSubaccounts_query","summary":"Query Ledger subaccounts","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseLedgerSubaccount"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/ledger-transactions":{"post":{"operationId":"LedgerTransactions_post","summary":"Post a ledger transaction","description":"Post a journal, with balance propagation and optional job costs.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.idempotencyKey"},{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerTransactionWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerTransactionInput"}}}}}},"/ledger-transactions/query":{"post":{"operationId":"LedgerTransactions_query","summary":"Query Ledger transactions","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseLedgerTransaction"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestLedgerTransaction"}}}}}},"/part-classes/query":{"post":{"operationId":"PartClasses_query","summary":"Query Part classes","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePartClass"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/parts/match":{"post":{"operationId":"Parts_match","summary":"Match Parts","description":"Fuzzy-match this entity by Levenshtein distance.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponsePart"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}}}}},"/parts/query":{"post":{"operationId":"Parts_query","summary":"Query Parts","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePart"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Inventory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/paygroups/query":{"post":{"operationId":"Paygroups_query","summary":"Query Paygroups","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePaygroup"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/payroll":{"post":{"operationId":"Payroll_post","summary":"Post a payroll batch","description":"Post a payroll batch: one paycheck and one balanced ledger transaction per check.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.idempotencyKey"},{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayrollWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayrollInput"}}}}}},"/payroll-calculations/query":{"post":{"operationId":"PayrollCalculations_query","summary":"Query Payroll calculations","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePayrollCalculation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/payroll-positions/query":{"post":{"operationId":"PayrollPositions_query","summary":"Query Payroll positions","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePayrollPosition"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/payroll-postings/query":{"post":{"operationId":"PayrollPostings_query","summary":"Query Payroll postings","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePayrollPosting"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/payroll-records/query":{"post":{"operationId":"PayrollRecords_query","summary":"Query Payroll records","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePayrollRecord"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/period-statuses/query":{"post":{"operationId":"PeriodStatuses_query","summary":"Query Period statuses","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePeriodStatus"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/pre-liens/query":{"post":{"operationId":"PreLiens_query","summary":"Query Pre liens","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePreLien"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/progress-billings/query":{"post":{"operationId":"ProgressBillings_query","summary":"Query Progress billings","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseProgressBilling"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Project documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestProgressBilling"}}}}}},"/proposals/query":{"post":{"operationId":"Proposals_query","summary":"Query Proposals","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseProposal"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Project documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestProposal"}}}}}},"/purchase-order-receipts":{"post":{"operationId":"PurchaseOrderReceipts_post","summary":"Receive against a purchase order","description":"Receive against a purchase order.\n\n**`Idempotency-Key` is REQUIRED here and optional on every other write**, and\nthis is the only operation in the API that spreads\n`WriteHeadersKeyRequired`. A request without one is refused with\n`IDEMPOTENCY_KEY_INVALID` at `400`. The reason is `updateQuantity`: it is\nadded to what the line has already received, and no layer anywhere can tell a\nretry from a second genuine receipt without a key.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"},{"$ref":"#/components/parameters/WriteHeadersKeyRequired.idempotencyKey"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderReceiptWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Purchasing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderReceiptInput"}}}}}},"/purchase-orders":{"post":{"operationId":"PurchaseOrderInsert_post","summary":"Create a purchase order","description":"Create a purchase order: the header and its lines, `the underlying record` + `the underlying record`.\n\n**No general ledger entry is made.** An order commits to a purchase; it does\nnot record one. Receive against it with `POST /purchase-order-receipts`,\nwhich is where inventory is debited, an AP invoice is generated and the job\ncosts are written.\n\nReusing an `orderNumber` for the same vendor is safe when the lines are\nidentical: the existing order is returned and nothing is written, so a\nrequest that timed out can be sent again. Reusing one with different lines is\na `409`.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.idempotencyKey"},{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderInsertWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Purchasing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderInsertInput"}}}}}},"/purchase-orders/query":{"post":{"operationId":"PurchaseOrderInsert_query","summary":"Query Purchase orders","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponsePurchaseOrder"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Purchasing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestPurchaseOrder"}}}}}},"/secondary-payees/query":{"post":{"operationId":"SecondaryPayees_query","summary":"Query Secondary payees","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseSecondaryPayee"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/service-contracts/query":{"post":{"operationId":"ServiceContracts_query","summary":"Query Service contracts","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServiceContract"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/service-equipment/query":{"post":{"operationId":"ServiceEquipmentRead_query","summary":"Query Service equipment","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServiceEquipment"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/service-inventories":{"post":{"operationId":"ServiceInventories_post","summary":"Create a service inventory","description":"Create a service inventory (work order/invoice), posting to the GL per status.","parameters":[{"$ref":"#/components/parameters/WriteHeaders.idempotencyKey"},{"$ref":"#/components/parameters/WriteHeaders.traceparent"},{"$ref":"#/components/parameters/WriteHeaders.dryRun"}],"responses":{"200":{"description":"A successful write.\n\n`200`, not `201`, and the name says succeeded rather than created because\nthree of the four things `201 Created` asserts are not true here:\n\n- **There is no `Location` to send.** `201` SHOULD carry one, and v3 has no\nGET-by-id route to point it at. The read surface is\n`POST /{entity}/query`. A `201` that can never populate `Location` offers\na caller nothing a `200` does not.\n- **Not every write creates.** `/purchase-order-receipts` updates an existing\npurchase order. Receiving against a purchase order is a mutation, and `201`\nwould be flatly wrong on it.\n- **An idempotent replay is not a creation.** `Idempotency-Status: replayed`\nbelow says so in the same response. With `201` as the happy path, one\nlogical operation would report two different success codes depending on\nwhether it was a retry, which is precisely the distinction a caller should\nnot have to branch on.\n\nWhat comes back is a result document (record numbers, the resolved period,\nthe GL and job-cost postings), not a pointer to a newly addressable resource.\nThat is `200`-shaped.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceInventoryWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceInventoryInput"}}}}}},"/service-inventories/query":{"post":{"operationId":"ServiceInventories_query","summary":"Query Service inventories","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServiceInventory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestServiceInventory"}}}}}},"/service-locations/query":{"post":{"operationId":"ServiceLocations_query","summary":"Query Service locations","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServiceLocation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/service-payments/query":{"post":{"operationId":"ServicePayments_query","summary":"Query Service payments","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServicePayment"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/service-schedules/query":{"post":{"operationId":"ServiceSchedules_query","summary":"Query Service schedules","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServiceSchedule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/service-types/query":{"post":{"operationId":"ServiceTypes_query","summary":"Query Service types","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseServiceType"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Service management"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/sub-change-order-lines/query":{"post":{"operationId":"SubChangeOrderLines_query","summary":"Query Sub change order lines","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseSubChangeOrderLine"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/subcontracts/query":{"post":{"operationId":"Subcontracts_query","summary":"Query Subcontracts","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseSubcontract"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Jobs and costing"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestSubcontract"}}}}}},"/tax-districts/query":{"post":{"operationId":"TaxDistricts_query","summary":"Query Tax districts","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseTaxDistrict"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Banking and tax"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/tax-entities/query":{"post":{"operationId":"TaxEntities_query","summary":"Query Tax entities","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseTaxEntity"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Banking and tax"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/timecard-calculations/query":{"post":{"operationId":"TimecardCalculations_query","summary":"Query Timecard calculations","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseTimecardCalculation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/timecard-lines/query":{"post":{"operationId":"TimecardLines_query","summary":"Query Timecard lines","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseTimecardLine"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/transaction-sources/query":{"post":{"operationId":"TransactionSources_query","summary":"Query Transaction sources","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseTransactionSource"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Ledger"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/units-completes/query":{"post":{"operationId":"UnitsCompletes_query","summary":"Query Units completes","description":"Query this entity. POST, because the filter tree is too large for a query string.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseUnitsComplete"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Project documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestUnitsComplete"}}}}}},"/utility/lock":{"post":{"operationId":"Utility_lock","summary":"Lock the database","description":"Disconnect all connections to Sage for maintenance.\n\n**This stops the customer working in Sage**, and it does not expire on its\nown; it is held until `/utility/un-lock` releases it. Take it for a period\nrotation or a restore, and release it in the same window.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LockResult"}}}},"400":{"$ref":"#/components/responses/BadRequest2"},"401":{"$ref":"#/components/responses/Unauthorized2"},"500":{"$ref":"#/components/responses/InternalError2"},"504":{"$ref":"#/components/responses/InternalError2"}},"tags":["Utility"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LockRequest"}}}}}},"/utility/ping":{"post":{"operationId":"Utility_ping","summary":"Ping","description":"Ping the on-premise agent and measure round-trip latency.\n\nAlso reports Sage schema compatibility, which is the reason to prefer this\nover timing any other request.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PingResult"}}}},"400":{"$ref":"#/components/responses/BadRequest2"},"401":{"$ref":"#/components/responses/Unauthorized2"},"500":{"$ref":"#/components/responses/InternalError2"},"504":{"$ref":"#/components/responses/InternalError2"}},"tags":["Utility"]}},"/utility/un-lock":{"post":{"operationId":"Utility_unlock","summary":"Unlock the database","description":"Reconnect to Sage, releasing the lock.\n\nOwnership is checked: releasing another user's lock is a `409` unless\n`override` is set.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LockResult"}}}},"400":{"$ref":"#/components/responses/BadRequest2"},"401":{"$ref":"#/components/responses/Unauthorized2"},"409":{"$ref":"#/components/responses/Conflict2"},"500":{"$ref":"#/components/responses/InternalError2"},"504":{"$ref":"#/components/responses/InternalError2"}},"tags":["Utility"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnlockRequest"}}}}}},"/vendor-activities/query":{"post":{"operationId":"VendorActivities_query","summary":"Query Vendor activities","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorActivity"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendor-balances/query":{"post":{"operationId":"VendorBalances_query","summary":"Query Vendor balances","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorBalance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendor-certificates/query":{"post":{"operationId":"VendorCertificates_query","summary":"Query Vendor certificates","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorCertificate"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendor-contacts/query":{"post":{"operationId":"VendorContacts_query","summary":"Query Vendor contacts","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorContact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendor-part-prices/query":{"post":{"operationId":"VendorPartPrices_query","summary":"Query Vendor part prices","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorPartPrice"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendor-remittances/query":{"post":{"operationId":"VendorRemittances_query","summary":"Query Vendor remittances","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorRemittance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendor-types/query":{"post":{"operationId":"VendorTypes_query","summary":"Query Vendor types","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendorType"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/vendors/match":{"post":{"operationId":"Vendors_match","summary":"Match Vendors","description":"Fuzzy-match this entity by Levenshtein distance.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponseVendor"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}}}}},"/vendors/query":{"post":{"operationId":"Vendors_query","summary":"Query Vendors","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseVendor"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Vendors and clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}},"/workers-comps/query":{"post":{"operationId":"WorkersComps_query","summary":"Query Workers comps","description":"Query this entity.\n\nPOST, not GET, and it stays POST: the filter tree is too large and too nested\nfor a query string. A read that is a POST is a smaller surprise than a URL\nthat has to be chunked.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponseWorkersComp"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"423":{"$ref":"#/components/responses/Locked"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalError"},"501":{"$ref":"#/components/responses/NotImplemented"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/GatewayTimeout"}},"tags":["Payroll"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequestBase"}}}}}}},"security":[{"ApiKeyAuth":[]}],"components":{"parameters":{"WriteHeaders.dryRun":{"name":"dryRun","in":"query","required":false,"description":"Run the write and roll it back instead of committing it.\n\nThe document is validated, the whole cascade executes against your live Sage\ndata inside one transaction (the general-ledger journal, the balance\npropagation, the job costs, the inventory movement), and then the transaction\nis rolled back. You get the same result body a real post would have returned,\nincluding the amounts and the accounts the posting would have touched, so\nthis answers \"what would this do\" against real data rather than against a\nguess.\n\n**No rows are committed. Two things are still consumed, and this is not a\ndefect we can fix from here.** Ledger reference numbers come from a SQL Server\nsequence and identity columns from the tables themselves, and neither is\ntransactional: a rolled-back transaction does not give them back. So a dry run\nleaves permanent gaps in those sequences, exactly as a genuinely failed post\ndoes. If gapless numbering matters to your auditors, do not dry-run in bulk\nagainst production.\n\n**Every identifier in a dry-run response is provisional and will never\nexist.** `recordNumber`, `id`, the ledger transaction's record number: all of\nthem were real inside the transaction and are gone after it. Do not store\nthem, and do not use them to correlate a later real post.\n\n`Idempotency-Key` is **ignored** on a dry run rather than rejected, so a\nclient that always sets one keeps working. Nothing is reserved and nothing is\nstored, which means a dry run never burns a key and never replays: post for\nreal afterwards with whatever key you like.\n\nThe response body carries `dryRun: true`, so it is self-describing and a\ncaller cannot mistake one for a committed write.","schema":{"type":"boolean","default":false},"explode":false},"WriteHeaders.idempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Idempotency key, Stripe-style.\n\nThe server stores the key with its response for **24 hours** and replays the\nstored response on a repeat. **This is the fix for the retry problem**: `504`\nis a documented, retryable outcome, and without a key a caller cannot tell\nwhether Sage committed.\n\nScope is `(API key, Idempotency-Key)`: per key, not per customer, so two\nintegrations at one customer that both mint keys from a natural identifier\ncannot collide. The **route and the request body are part of the fingerprint,\nnot the key**: the same key sent with a different body, or to a different\nendpoint, is `409 IDEMPOTENCY_KEY_REUSED` rather than a wrong-shaped replay.\nThe fingerprint is taken over the decoded request, so re-serialising the same\npayload with different whitespace or key order is the same request.\n\n**What is stored is the response that was sent, and it is stored if and only\nif the request reached the on-premise service.** So a `422` or a `502` is\nstored too, and a caller who corrects the body must use a **new** key. Nothing\ndecided before dispatch (an unknown field, a validation failure, a missing\nscript) burns the key, so the corrected retry executes rather than colliding.\n\nReplays are marked `Idempotency-Status: replayed`. A concurrent duplicate is\nrefused with `409 IDEMPOTENCY_KEY_IN_FLIGHT` and a `Retry-After` rather than\nbeing executed twice.\n\nAt most 128 characters, printable only. Recommended but not required here, so\nthat a first integration is not blocked on generating one; a write without a\nkey is not replay-safe and the contract says so rather than pretending\notherwise. It **is** required on `/purchase-order-receipts`.","schema":{"type":"string","maxLength":128}},"WriteHeaders.traceparent":{"name":"traceparent","in":"header","required":false,"description":"W3C trace context. Send it and it is propagated to the on-premise service\nand into the server logs for the request.\n\nv2's bespoke `x-context-id` and `x-integration-id` headers do not exist in\nv3; use this instead.","schema":{"type":"string"}},"WriteHeadersKeyRequired.idempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"As `WriteHeaders.idempotencyKey`, but required.","schema":{"type":"string","minLength":1,"maxLength":128}}},"schemas":{"AllocationPurpose":{"type":"string","enum":["JOB_REQUISITION","LOCATION_TRANSFER","OTHER"],"description":"Why an allocation exists, and therefore whether it produces a job cost.\n\nIn v2 this was inferred from the allocation's free-text `description`, which\nhad to read exactly `\"To Requisition Stock to Job\"` for a job cost to be\nwritten. In v3 it is stated explicitly with this field, and the description is\nfree text with no effect on posting.","x-enum-varnames":["JobRequisition","LocationTransfer","Other"]},"ApInvoiceInput":{"type":"object","required":["enteredDate","invoiceNumber","description","vendorNumber","invoiceDate","invoiceType","invoiceTotal","subTotal","invoiceAmount","invoiceBalance","invoiceNet","lines"],"properties":{"userName":{"type":"string","description":"The Sage user the posting is attributed to. Falls back to the SQL login when absent."},"enteredDate":{"type":"string","format":"date","description":"``entdte``. **Drives the posting period**: the posting date, not the\ndocument date. `invoiceDate` does not affect which period the invoice lands\nin."},"invoiceNumber":{"type":"string","minLength":1,"description":"``invnum``. Caller-supplied and, like `LedgerTransactionInput.transactionNumber`,\n**not validated for uniqueness by OPDC**. Also becomes ``trnnum`` and\nthe `the underlying record` transaction number/description when the invoice posts."},"description":{"type":"string","minLength":1,"description":"``dscrpt``, and the GL journal's header description when the invoice posts."},"shipmentNumber":{"type":"string","description":"Accepted and inert. v2 documented this as a shipment/receiving number, but\nit is not written to any column and not echoed on any output, and there is\nno Sage column it maps to. Kept only because the v2 field existed."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"``vndnum``. Also looked up against `the underlying record` for payment terms; see `dueDate`."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"``jobnum``. Absent (or `0` in v2) means no job costing: the whole per-line job-cost cascade is skipped."},"invoiceDate":{"type":"string","format":"date","description":"``invdte``. Also the GL transaction date and the job costs' transaction date when the invoice posts."},"dueDate":{"type":"string","format":"date","description":"**Ignored and overwritten.** ``duedte`` is computed server-side from\nthe vendor's `the underlying record` payment terms, falling back to `invoiceDate` when the\nvendor or its terms are missing. Retained because v2 accepted it; do not\nrely on it to set the due date."},"invoiceType":{"$ref":"#/components/schemas/ApInvoiceType"},"status":{"$ref":"#/components/schemas/ApInvoiceStatus","description":"Absent means `1` (Open). See `ApInvoiceStatus`.","default":1},"invoiceTotal":{"$ref":"#/components/schemas/Decimal"},"subTotal":{"$ref":"#/components/schemas/Decimal"},"invoiceAmount":{"$ref":"#/components/schemas/Decimal"},"invoiceBalance":{"$ref":"#/components/schemas/Decimal"},"invoiceNet":{"$ref":"#/components/schemas/Decimal"},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"IGNORED. The posting period is resolved solely from `enteredDate`; there is\nno way to override it on this endpoint, unlike\n`LedgerTransactionInput.period`. Accepted for v2 input compatibility only."},"year":{"type":"integer","format":"int16","description":"IGNORED, for the same reason as `period`."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ApInvoiceLineInput"},"minItems":1}},"description":"An AP invoice to insert (`the underlying record` + `the underlying record`), optionally cascading a GL\njournal (`the underlying record`/`the underlying record`, with `the underlying record`/`the underlying record`/`the underlying record` balance\npropagation) and one `the underlying record` per line when `jobNumber` is set and the invoice\nposts."},"ApInvoiceLineInput":{"type":"object","required":["partDescription","lineQuantity","linePrice","extendedTotal"],"properties":{"lineNumber":{"type":"integer","format":"int32","minimum":1,"description":"Auto-assigned to the line's 1-based position in `lines` when omitted."},"partNumber":{"$ref":"#/components/schemas/RecordNumber","description":"``prtnum``, the part's Sage record number. Absent when the line is not a stocked part."},"partDescription":{"type":"string","description":"``prtdsc``. Also read back by the header-level discount calculation:\na line whose description contains the substring `\"tax\"` (case-insensitive)\nis excluded from the discountable amount. Free text, not a flag: there is\nno structured \"is this a tax line\" field."},"alphaNumber":{"type":"string"},"unitDescription":{"type":"string"},"lineQuantity":{"$ref":"#/components/schemas/Decimal"},"linePrice":{"$ref":"#/components/schemas/Decimal"},"extendedTotal":{"$ref":"#/components/schemas/Decimal","description":"``extttl``. Also the amount debited on the GL journal line and the\n`costAmount` of the job cost this line produces. **Not cross-checked**\nagainst `lineQuantity * linePrice`. Unlike `InventoryAllocationLineInput.total`,\nthis value is accepted on trust, exactly as v2 did."},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"``actnum``. Absent means this line writes no GL debit; see the model doc above."},"subAccountNumber":{"type":"integer","format":"int64","description":"``subact``. Absent (or `0` in v2) means no sub-account."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"IGNORED for GL purposes. The job cost this line produces always writes\n``cstcod`` as `1` regardless of this value. Retained on the contract\nbecause v2 already accepted it as caller input; do not rely on it to change\nthe posted cost code."},"costType":{"type":"integer","format":"int16","minimum":1,"description":"``csttyp`` on the job cost this line produces, when `jobNumber` is\nset on the header. Absent means `1`. Install-specific above 5; query the\ncost-type lookup rather than hardcoding.","default":1},"billingStatus":{"$ref":"#/components/schemas/JobCostBillingStatus","description":"`JobCostBillingStatus` on the job cost this line produces.\n\n**Not settable in v2**, which had no such field. Absent means `1`.","default":1},"billingAmount":{"$ref":"#/components/schemas/Decimal","description":"Billing amount on the job cost this line produces. Not settable in v2, for\nthe same reason as `billingStatus`."},"status":{"$ref":"#/components/schemas/JobCostStatus","description":"`JobCostStatus` on the job cost this line produces, when `jobNumber` is\nset on the header. Absent means `1`.","default":1}},"description":"One AP invoice line (`the underlying record`).\n\nA line with no `accountNumber` is still inserted, but produces no GL debit. It\nstill produces a job cost when `jobNumber` is set on the header."},"ApInvoiceLineResult":{"type":"object","required":["id","recordNumber","lineNumber","partDescription","lineQuantity","linePrice","extendedTotal"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"partNumber":{"$ref":"#/components/schemas/RecordNumber"},"partDescription":{"type":"string"},"alphaNumber":{"type":"string"},"unitDescription":{"type":"string"},"lineQuantity":{"$ref":"#/components/schemas/Decimal"},"linePrice":{"$ref":"#/components/schemas/Decimal"},"extendedTotal":{"$ref":"#/components/schemas/Decimal"},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber"},"subAccountNumber":{"type":"integer","format":"int64"}},"description":"One AP invoice line as written."},"ApInvoiceResult":{"type":"object","required":["id","recordNumber","userName","enteredDate","invoiceNumber","description","vendorNumber","invoiceDate","dueDate","discountAmount","invoiceType","status","invoiceTotal","subTotal","invoiceAmount","invoiceBalance","invoiceNet","lines"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"userName":{"type":"string"},"enteredDate":{"type":"string","format":"date"},"invoiceNumber":{"type":"string"},"description":{"type":"string"},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber"},"jobNumber":{"$ref":"#/components/schemas/RecordNumber"},"invoiceDate":{"type":"string","format":"date"},"dueDate":{"type":"string","format":"date","description":"As resolved server-side; see `ApInvoiceInput.dueDate`."},"discountDate":{"type":"string","format":"date","description":"Present only when the vendor's terms produced a discount date."},"discountAmount":{"$ref":"#/components/schemas/Decimal","description":"`0.00` when no discount applies."},"invoiceType":{"$ref":"#/components/schemas/ApInvoiceType"},"status":{"$ref":"#/components/schemas/ApInvoiceStatus"},"invoiceTotal":{"$ref":"#/components/schemas/Decimal"},"subTotal":{"$ref":"#/components/schemas/Decimal"},"invoiceAmount":{"$ref":"#/components/schemas/Decimal"},"invoiceBalance":{"$ref":"#/components/schemas/Decimal"},"invoiceNet":{"$ref":"#/components/schemas/Decimal"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ApInvoiceLineResult"}}},"description":"An AP invoice as written, header and lines.\n\nIncludes `subTotal`/`invoiceAmount`/`invoiceBalance`/`invoiceNet`, all real\n`the underlying record` columns that v2 silently dropped from its response despite accepting\nand writing them on input. The GL journal and job costs this invoice produced\nare reported via `WriteResult`'s own `ledgerTransaction`/`jobCosts` fields,\nnot duplicated here."},"ApInvoiceStatus":{"type":"integer","format":"int16","description":"``status``, against Sage's \"About payable invoice status\".\n\nOnly 1, 2 and 3 are user-assignable; Sage sets 4 and 5 itself.\n\n**Status decides whether a journal exists.** The write path posts the general\nledger cascade at `1-Open` only. At any other status the invoice is stored as\nthe underlying record plus the underlying record with no journal, no balance propagation, no job costs, and\n`ledgerRecord` left at `0`. A value outside 1..5 is currently accepted and not\nrejected.","x-enum-descriptions":["Open. Indicates a record posted to the general ledger.","Review. Indicates the management or bookkeeping staff should review the record.","Dispute. Indicates a record disputed by the vendor.","Paid. Indicates a record paid in full. Set by Sage, not by a caller.","Void. Indicates a void record. Set by Sage, not by a caller."],"x-enum-varnames":["Open","Review","Dispute","Paid","Void"],"enum":[1,2,3,4,5]},"ApInvoiceType":{"type":"integer","format":"int16","description":"``invtyp``, against Sage's \"About receivable invoice types\".\n``invtyp`` carries the same two values.\n\nEarlier v2 documentation called these \"Standard\" and \"Credit memo\", which\nobscures what they do: the value decides whether the invoice moves the job's\nbilling.\n\nNot to be confused with ``invtyp``, the service invoice type, which is\nconfigured per company and so is not an enumeration here.","x-enum-descriptions":["Contract. The invoice affects the billing for the contract; it increases the job balance.","Memo. The invoice does not affect job billing. Deposits, bad debts."],"x-enum-varnames":["Contract","Memo"],"enum":[1,2]},"ApInvoiceWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun","period","year"],"properties":{"document":{"$ref":"#/components/schemas/ApInvoiceResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into."}},"description":"The body of a successful `POST /ap-invoices`.\n\nThe job costs this invoice produced are reported on the envelope's `jobCosts`\narray rather than nested inside `ledgerTransaction`."},"Assembly":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"assemblyName":{"type":"string","description":"`asmnme`. The assembly's name."},"assemblyUnit":{"type":"string","description":"`asmunt`. The unit the assembly is costed and sold in."},"assemblyClassNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The assembly class's Sage record number (`asmcls`), a foreign key to\n`the underlying record`.\n\nInstall-specific: the classes are configured per company, so no values are\nnamed here; query the assembly-class lookup (`the underlying record`,\n`/query/assembly-class`)."},"totalCost":{"$ref":"#/components/schemas/Decimal","description":"`ttlcst`. The rolled-up cost of the assembly's parts."},"userName":{"type":"string"},"noteText":{"type":"string"},"parts":{"type":"array","items":{"$ref":"#/components/schemas/AssemblyPart"},"description":"Child collection `parts`, requested via `children: [\"parts\"]`.\n\nThe exception described in this model's doc comment: every other entity\nspells its child collection `lines`, and on this entity `lines` is not a\nvalid value."}},"description":"`the underlying record`, an assembly (a kit of parts), as read.\n\n`id` and `recordNumber` are always present; **every other field is optional**\nbecause a v3 query can restrict the response with `select`, so a field's\nabsence means \"not requested\" rather than \"not set\".\n\n**This entity is the one deliberate exception to the collection-naming rule.**\nEvery other entity spells its child collection `lines`; this one spells it\n`parts`, so it is requested with `children: [\"parts\"]` and `children: [\"lines\"]`\nis invalid here.\n\nThe exception is kept for two reasons. They are parts, not lines: an assembly\nhas no line numbers in Sage's sense, and calling them `lines` would make the\nresponse read as a document with line items, which an assembly is not. And\nrenaming it would break every existing caller that requests children, bought\nonly for the sake of uniformity."},"AssemblyClass":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). This is the value\n`Assembly.assemblyClassNumber` carries."},"className":{"type":"string","description":"`clsnme`, the class name, and the whole reason to query this entity."}},"description":"`the underlying record`, the assembly-class lookup.\n\nThree columns, and it exists to be resolved against:\n`Assembly.assemblyClassNumber` (`asmcls`) is a foreign key to this table's\n`recnum`, and because the classes are configured per company no values can be\nnamed in the spec. This entity is the endpoint that resolves them.\n\n**The lookups an integrator needs are not all routed, and this one is.** Worth\nsaying because `Subcontract.contractType` and `PurchaseOrder.orderType` point\nat `SBCTYP` and its equivalent, for which no lookup endpoint exists at all, so\nthose codes are currently unresolvable. Assembly class is not in that position.\n\n**Only `id` and `recordNumber` are required.** `className` is optional because\na query may narrow the row with `select`, not because a class can be unnamed."},"AssemblyPart":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"lineNumber":{"type":"integer","format":"int32"},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The referenced part's Sage record number (`prtnum`), **not** its alphanumeric\ncode.\n\n`Part.recordNumber` is what this points at. The human-facing code is\n`Part.partCode`."},"partName":{"type":"string","description":"`prtnme`, the part's name as Sage stores it on the assembly line."},"partUnit":{"type":"string","description":"`prtunt`, the unit the part is counted in on the assembly line."},"quantity":{"$ref":"#/components/schemas/Decimal","description":"How many of the part the assembly contains.\n\nThe column is `prtqty`, published as `quantity` here and as `partQuantity` on\n`the underlying record` and `the underlying record`: the same data under two names."},"billingAmount":{"$ref":"#/components/schemas/Decimal","description":"The part's billing amount (`prtbil`).\n\n**`cost` in v2, which is wrong**: the same column is published as\n`billingAmount` on `the underlying record`, and it is a billing figure, not a cost. A caller\nreading it as a cost gets a marked-up figure."},"extendedPrice":{"$ref":"#/components/schemas/Decimal","description":"`extprc`. The majority spelling; `the underlying record` publishes it as `extPrice`."},"userDefined1":{"type":"string"}},"description":"`the underlying record`, one part in an assembly, as read.\n\n`_idref`, the parent UUID, is not exposed: the collection is only reachable\nnested under its assembly, whose `id` already carries it."},"BankReconciliation":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). This row's own; the underlying record is not a child table."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lgract`, a foreign key to ``recnum``: the cash account being\nreconciled.\n\nSage labels ``recnum`` \"Account#\", so the record number here *is* the\naccount number a human reads. A reconciliation always has one."},"description":{"type":"string","description":"`dscrpt`."},"saveDate":{"type":"string","format":"date","description":"`svedte`, Sage's \"Date\": when the reconciliation was saved. A date, not an\ninstant.\n\n**Not a modification stamp.** It is a business date a user chooses, so this\nentity offers no watermark and a `lastUpdated` filter on it is answered with\na 501 rather than an approximation."},"statementDate":{"type":"string","format":"date","description":"`stmdte`, Sage's \"Statement Ending Date\"."},"cutoffDate":{"type":"string","format":"date","description":"`cutdte`, Sage's \"Cutoff Date\": transactions after this date are excluded from\nthe reconciliation."},"currentBalance":{"$ref":"#/components/schemas/Decimal","description":"`curbal`, `Numeric(9,2)`. The ledger's balance for the account. JSON string, native scale."},"statementBeginBalance":{"$ref":"#/components/schemas/Decimal","description":"`stmbeg`, Sage's \"Statement Beginning Balance\"."},"statementEndBalance":{"$ref":"#/components/schemas/Decimal","description":"`stmend`, Sage's \"Statement Ending Balance\". The figure `lines` has to reconcile to."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/BankReconciliationLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a bank reconciliation, as read.\n\nOne saved reconciliation of one cash account against one statement: Sage menu\n1-5. The header carries the account and the three balances; `lines` carries what\ncleared.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a request may narrow the response with `select`.\n\n`the underlying record` is this table column for column, and the two are still separate models\nbecause their lines are not: `the underlying record` splits the amount into a credit and a\ncharge and carries a subaccount. See `CreditCardReconciliation`."},"BankReconciliationLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"idRef":{"type":"string","description":"The parent reconciliation's `id` (`_idref`), the column the child fetch joins on."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``. See the model doc: not this row's own number."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"ledgerRecord":{"$ref":"#/components/schemas/RecordNumber","description":"`lgrrcd`, a foreign key to ``recnum``: the ledger transaction this line\nclears.\n\n**The column is `lgrrcd`, not `lgrrec`, and that is not a typo.** Most tables\nin the product spell this concept `lgrrec`; `the underlying record` and `the underlying record` are the two\nwhere Sage itself spells it `lgrrcd`. All of them reach a caller under the\none field name `ledgerRecord`."},"lineType":{"type":"integer","format":"int16","description":"`lintyp`, Sage `Numeric(1)`.\n\n**Deliberately not an enum**: neither Sage's published help nor this\nspecification documents the value set, so the meanings have to be read off\nthe install."},"isPriorYear":{"type":"integer","format":"int16","description":"`ispryr`, Sage's \"Is Prior Year\". A `Yes/No` column arriving as `0` or `1`.\n\nDespite the `is` prefix the wire carries the integer, not a boolean."},"transactionNumber":{"type":"string","description":"`trnnum`, Sage's \"Transaction#\", `Character(20)`: a document number, not an integer."},"payee":{"type":"string","description":"`payee_`. The trailing underscore is Sage escaping a reserved word, as in\n`state_` and `e_mail`; it never reaches the wire. `the underlying record` and `the underlying record` spell\nthe same concept `payee1` and also publish it as `payee`."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, `Numeric(9,2)`. Signed: a cheque and a deposit are the same column. JSON string, native scale."},"transactionHash":{"type":"string","description":"`trnhsh`, `Character(64)`: the hash of the imported bank transaction this\nline was matched to.\n\n**This is the join to `BankingTransaction`**, which Sage keys on the same\n64-character `trnhsh` rather than on a record number. Empty on a line\nreconciled by hand rather than matched against a feed."}},"description":"`the underlying record`, a bank reconciliation line, as read.\n\nOne cleared item on a statement: the ledger transaction it corresponds to, the\namount, and (when the statement was imported rather than keyed) the hash of\nthe bank transaction it was matched against.\n\n`recordNumber` is the **parent's** record number, not this row's: Sage keys\nthe underlying record on `recnum` targeting ``recnum``, as every line table in the product\ndoes. Redundant when read nested under the parent, which is the only way it can\nbe read, but the column is in the child `SELECT` and therefore on the wire."},"BankingTransaction":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"transactionHash":{"type":"string","description":"`trnhsh`, `Character(64)`. **Sage's primary key for this table** and the join\nto a reconciliation line; see the model doc."},"transactionId":{"type":"integer","format":"int64","description":"`trn_id`, `Numeric(10)`: the bank's own identifier for the transaction.\n\n**Not an identifier this API issued and not a Sage record number.** It is an\nexternal system's number, meaningful only against that bank. It is\ndeliberately not called `recordNumber`, which would claim it is Sage's, nor\n`transactionNumber`, which is a document number elsewhere on this surface."},"bankId":{"type":"string","description":"`bnk_id`, `Character(36)`: which bank connection the row came from. An external identifier, like `transactionId`."},"transactionType":{"type":"string","description":"`trntyp`, `Character(20)`.\n\n**A string, and that is Sage's own type here**, not a coded integer like the\ntype and status columns on every Sage-authored document. The values come from\nthe feed, so they are the bank's vocabulary and not an enum this\nspecification could close."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, `Numeric(9,2)`. JSON string, native scale."},"checkNumber":{"type":"string","description":"`chknum`, `Character(25)`. A cheque number as the bank reported it: a string, and often not numeric."},"referenceNumber":{"type":"string","description":"`refnum`, `Character(32)`. The bank's reference for the item."},"transactionName":{"type":"string","description":"`trnnme`, `Character(32)`, Sage's \"Name\": the counterparty as the bank described it. Free text, not a vendor."},"postedDate":{"type":"string","format":"date","description":"`pstdte`, Sage's \"Posted Date\": when the bank posted it. A date, not an\ninstant.\n\nThe bank's date, not a Sage entry date, and it is the only date on the row.\nThere is nothing here recording when the import ran, so this entity offers no\nwatermark and a `lastUpdated` filter on it is a 501."}},"description":"`the underlying record`, Sage's \"Banking Cloud Transactions\", as read.\n\nThe bank feed: transactions imported from the bank, before anyone decides what\nthey are. A row here is not a Sage document (it has no ledger account, no\nperiod and no journal), and it becomes one only when a reconciliation line\nmatches it.\n\n**This entity has no `recnum` and no document number.** Sage keys the table on\n`trnhsh`, a 64-character hash, which is the only key an import from an external\nsystem can have: the hash is what makes re-importing the same statement\nidempotent. So `id` is the only required field, and `transactionHash`, not\n`recordNumber`, is what a caller joins on.\n\n**`BankReconciliationLine.transactionHash` and\n`CreditCardReconciliationLine.transactionHash` are the other side of that\njoin**: same column name, same `Character(64)`. It is the route from \"the bank\nsays this cleared\" to \"Sage posted it here\".\n\nSage carries columns this API does not select: the payee id (`pay_id`), the\nfour address columns, and the extended name (`extnme`)."},"Benefit":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The row's only identifier; see the model note."},"paygroup":{"$ref":"#/components/schemas/RecordNumber","description":"`paygrp`, a foreign key to ``recnum``: the `Paygroup` entity's\n`recordNumber`. The filter to read one paygroup's benefits, since `the underlying record`\nreturns no nested collection.\n\nSee `Employee.paygroup` for why the name carries no `Number` suffix."},"calculationNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`dednum`, a foreign key to ``recnum``: the `PayrollCalculation`\nentity's `recordNumber`. Sage names the column for deductions; the table it\npoints at holds every calculation, deduction or not.\n\n**Named `calculationNum` in v2, published as `calculationNumber` here**, the\nsame spelling `EmployeeEarnings` and `TimecardCalculation` use, so that one\ntarget table is reached under one field name from all three. Note that\n`dednum` is a **different column** from the `clcnum` the other two carry;\nnaming them alike is a deliberate convergence on the concept."},"deductionRate":{"$ref":"#/components/schemas/Decimal","description":"`dedrte`, the rate this paygroup applies to the calculation, overriding\n`PayrollCalculation.defaultRate` for employees in the group.\n\nA rate, not an amount. OPDC never computes with it. See the model note."}},"description":"`the underlying record`, a paygroup benefit, as read.\n\nThe join between a paygroup and the payroll calculations it applies, carrying\nthe rate for each: three columns and a UUID. It is what makes a paygroup worth\nassigning: an employee in a union or certified-payroll group inherits these\ncalculations and rates rather than carrying them row by row in\n`EmployeeEarnings`.\n\n**This model has no `recordNumber`.** The table has no `recnum` column for the\nquery to alias; the row's identity on the wire is `id`, and `paygroup` +\n`calculationNumber` is its natural key. `DailyPayroll` is the only other\npayroll entity in this state; every other one either has its own `recnum` or\npublishes its parent's.\n\n**Read-only in every sense.** OPDC never writes `the underlying record`, and a payroll post\nnever consults it: the provider has already applied whatever benefit\ndeductions a paygroup implies before the batch arrives, and each deduction's\namount and liability account are taken from the request. So a mismatch between\nthis table and what a provider withheld is invisible to the post, unlike the\npay rates in `the underlying record`, which are reconciled and will fail a batch.\n\n`the underlying record` registers no child relation, so no `children` argument.\n\n**Only `id` is guaranteed**; `select` may narrow the rest."},"Budget":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`), which for a budget is the job number."},"lockEdit":{"type":"integer","format":"int16","description":"Sage's edit lock (`lckedt`, a one-digit flag).\n\nDeliberately not an enum and deliberately not re-encoded as a boolean: the\nfull domain of this column has not been verified, so it is published as the\ninteger Sage stores."},"editDate":{"type":"string","format":"date","description":"When the budget was last edited (`edtdte`).\n\nA date, not an instant. This is also the column `budget` supports\n`lastUpdated` incremental sync on, one of only seven entities that support\nit at all."},"userName":{"type":"string","description":"The Sage user who last edited the budget (`usrnme`)."},"noteText":{"type":"string","description":"Budget note (`ntetxt`)."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/BudgetLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a job budget, as read.\n\nOne budget per job: Sage keys `the underlying record` on `recnum` and that record number is\nthe job number (``recnum``), which is why there is no separate job field\non this entity.\n\nOnly `id` and `recordNumber` are required. Every other field is optional\nbecause a read may restrict the response with `select`, so its absence means\n\"not asked for\", not \"not set\"."},"BudgetLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The parent budget's record number (`recnum`), which is the job number.\nRepeated on every line of the budget; see the note above."},"phaseNumber":{"type":"integer","format":"int64","description":"The job phase this line budgets (`phsnum`), a foreign key to the job-phase\nlookup. It is ``phsnum``, not ``recnum``: that column is the job\nnumber."},"lineNumber":{"type":"integer","format":"int32","description":"Position within the phase (`linnum`)."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Not an enum; query the cost-code lookup."},"hoursBudget":{"$ref":"#/components/schemas/Decimal","description":"Budgeted hours (`hrsbdg`)."},"materialBudget":{"$ref":"#/components/schemas/Decimal","description":"Budgeted material cost (`matbdg`)."},"laborBudget":{"$ref":"#/components/schemas/Decimal","description":"Budgeted labor cost (`labbdg`)."},"equipmentBudget":{"$ref":"#/components/schemas/Decimal","description":"Budgeted equipment cost (`eqpbdg`)."},"subBudget":{"$ref":"#/components/schemas/Decimal","description":"Budgeted subcontract cost (`subbdg`). Sage labels this column \"Subcontract\"."},"otherBudget":{"$ref":"#/components/schemas/Decimal","description":"Budgeted other cost (`othbdg`)."},"totalBudget":{"$ref":"#/components/schemas/Decimal","description":"The budget total for this cost code (`ttlbdg`).\n\n**This is the single v3 spelling of the concept.** The same value is copied\nonto the cost-to-complete line (``bdgttl``), where v2 spelled it\n`budgetTotal`; v3 spells both `totalBudget`, matching the six sibling\n`*Budget` fields above."},"unitDescription":{"type":"string","description":"Unit of measure for `estimatedUnits` (`untdsc`)."},"estimatedUnits":{"$ref":"#/components/schemas/Decimal","description":"Estimated quantity (`estunt`)."},"unitCost":{"$ref":"#/components/schemas/Decimal","description":"Cost per unit (`untcst`)."},"userDefined1":{"type":"string","description":"Free text, per install (`usrdf1`)."},"noteText":{"type":"string","description":"Line note (`ntetxt`)."}},"description":"`the underlying record`, a budget line, as read.\n\nOne row per phase + cost code. Note that `recordNumber` is **not** unique to a\nline: Sage keys `the underlying record` on the parent's `recnum` (the job number, see\n`Budget`), so line identity is the triple `recordNumber` + `phaseNumber` +\n`lineNumber`.\n\nEvery decimal below is a JSON **string** at native scale. The v2 spec declared\nthem `\"type\": \"number\"` while the wire carried a string, which is the defect\nthat makes a strictly-typed generated client fail to deserialize a budget."},"CardIssuer":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``: the card's liability account.\nSee the model doc: this is an account number, not an ordinary record number."},"payee":{"type":"string","description":"`payee1`, Sage's \"Payee\": who the card payment is made out to.\n\nThe same column name `the underlying record` uses for its payee, and published under the same\nfield name. `the underlying record` and `the underlying record` spell their equivalent `payee_`; all three\nreach a caller as `payee`."},"addressLine1":{"type":"string","description":"`addrs1`."},"addressLine2":{"type":"string","description":"`addrs2`."},"cityName":{"type":"string","description":"`ctynme`. City alone, unlike `VendorRemittance.cityState`, which packs city and state into one column."},"stateCode":{"type":"string","description":"`state_`. Two-letter state or province code; the underscore is Sage escaping a reserved word."},"zipCode":{"type":"string","description":"`zipcde`."}},"description":"`the underlying record`, Sage's \"Card Issuer Remit Information\", as read.\n\nWhere the payment for a credit card goes: menu 4-7-1, one row per card.\n\n**`recordNumber` is a ledger account number.** Sage keys the underlying record on `recnum`\ntargeting ``recnum`` and labels the column \"Credit Card Issuer\", so the\ncard's liability account *is* the key of its issuer record. That makes the join\nto a reconciliation direct: `CardIssuer.recordNumber` equals\n`CreditCardReconciliation.ledgerAccount`.\n\nThe table is a small address book and nothing else: there is no card number, no\ncredit limit and no vendor link on it."},"ChangeOrder":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"changeNumber":{"type":"string","description":"`chgnum`. Sage's change order number, a string, not the record number."},"changeDate":{"type":"string","format":"date","description":"Date, in both directions. v2 read it back as `2026-01-15T00:00:00Z`."},"enteredDate":{"type":"string","format":"date","description":"Date, not an instant. Same v2 defect as `changeDate`."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"Sage's job record number."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`. Already the suffixed spelling in v2, and the one all ten models that carry it use."},"description":{"type":"string"},"changeType":{"type":"integer","format":"int16","description":"`chgtyp`, the change order type.\n\n**Published as an integer with no member list**, because the values have not\nbeen verified against Sage."},"status":{"type":"integer","format":"int16","description":"`status`.\n\n**Published as an integer with no member list**, for the same reason as\n`changeType`. Do not assume it matches any other status family in this API."},"requestedAmount":{"$ref":"#/components/schemas/Decimal"},"approvedAmount":{"$ref":"#/components/schemas/Decimal"},"budgetAmount":{"$ref":"#/components/schemas/Decimal","description":"`cstamt`, which on `the underlying record` holds the change order's **budget**, not a cost,\nwhich is why it is not published as `costAmount` the way the column is\nelsewhere."},"overheadAmount":{"$ref":"#/components/schemas/Decimal"},"profitAmount":{"$ref":"#/components/schemas/Decimal"},"period":{"type":"integer","format":"int16","description":"The period the row was posted into."},"year":{"type":"integer","format":"int16","description":"`postyr`. Spelled `postingYear` on some v2 read models; `year` is the v3 spelling."},"userName":{"type":"string"},"noteText":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ChangeOrderLine"},"description":"Child collection `lines`, requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a project management change order, as read.\n\n`id` and `recordNumber` are always present; **every other field is optional**\nbecause a v3 query can restrict the response with `select`, so a field's\nabsence means \"not requested\" rather than \"not set\".\n\n`children: [\"lines\"]` is the only valid children value for this entity."},"ChangeOrderLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"lineNumber":{"type":"integer","format":"int32"},"description":{"type":"string"},"estimatedAmount":{"$ref":"#/components/schemas/Decimal","description":"The estimated amount for this change order line."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, a foreign key to ``recnum``. Install-specific, so not an\nenum; query the cost-code lookup (`/query/cost-code`)."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract, `5` Other are Sage\npresets. `6`-`9` are defined per company and are deliberately not named here;\nquery the cost-type lookup (`/query/cost-type`) for the install's labels."},"userDefined1":{"type":"string"}},"description":"`the underlying record`, a change order line, as read.\n\n**Never published in the v2 spec**, neither the array nor this schema, so a v2\nreader could not discover these field names.\n\n`_idref`, the parent UUID, is not exposed: the collection is only reachable\nnested under its parent, where the parent's `id` already carries it. Compare\n`SubChangeOrderLine`, which does expose it, and see the note there."},"Client":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): the client number a human reads in Sage.\n\nThis is the value other entities carry as `clientNumber` (`Job.clientNumber`,\nand eight further models on `clnnum`)."},"shortName":{"type":"string","description":"`shtnme`. The abbreviated name Sage shows in pick lists."},"clientName":{"type":"string","description":"`clnnme`."},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string"},"zipCode":{"type":"string"},"billingAddressLine1":{"type":"string","description":"`bilad1`, the billing address.\n\n**`billAddress1` in v2.** The whole billing group is renamed, so that the\nbilling address and the primary address are spelled the same way. The Sage\ncolumns are unchanged."},"billingAddressLine2":{"type":"string","description":"`bilad2`. `billAddress2` in v2; see `billingAddressLine1`."},"billingCityName":{"type":"string","description":"`bilcty`. `billCity` in v2."},"billingStateCode":{"type":"string","description":"`bilste`. `billState` in v2."},"billingZipCode":{"type":"string","description":"`bilzip`. `billZip` in v2."},"userDefined1":{"type":"string","description":"`usrdf1`. Free text, meaning defined per install."},"userDefined2":{"type":"string","description":"`usrdf2`. Free text, meaning defined per install."},"contact1":{"type":"string","description":"`contct`. The primary contact's name, free text.\n\n**The numbering is deliberate and is kept.** `the underlying record` is the one table that\ncarries three contacts, so `contact1` here and a bare `contact` on other\nentities are different data rather than an inconsistency. The same reasoning\nholds the `1` suffix on `phone1`, `fax1`, `cell1` and `email1` below, none of\nwhich has a modelled `2`."},"contact2":{"type":"string","description":"`contc2`. The second contact's name."},"contact3":{"type":"string","description":"`contc3`. The third contact's name."},"phone1":{"type":"string","description":"`phnnum`. Free text as Sage stores it; no format is imposed. See `contact1` on the suffix."},"fax1":{"type":"string","description":"`faxnum`."},"cell1":{"type":"string","description":"`cllphn`."},"email1":{"type":"string","description":"`e_mail`."},"employeeNumber":{"type":"integer","format":"int64","description":"`empnum`. The employee assigned to this client, in the salesperson role.\n`0` when unset.\n\n**`salesperson` in v2.** `empnum` was published under three role names across\nthe v2 read surface (`employeeNumber`, `salesperson`, `technician`); v3 gives\nthe column one name. `Job.salesperson` is **not** the same column (a job has\nits own `slsemp`), so it keeps its name."},"manager":{"type":"integer","format":"int64","description":"`mannum`. The managing employee's record number. `0` when unset.\n\nA different column from `empnum`, and unrelated to ``mannum``, which is\na manufacturer part number on the part master."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``: Sage's tax district table.\nInstall-specific and **not an enum**. It is never validated on write, so a\ncaller should check the number against the tax districts before posting it."},"discountTerms":{"type":"string","description":"`dsctrm`. A terms string, not a date."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"`dscrte`. JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"dueTerms":{"type":"string","description":"`duetrm`. A terms string, not a date."},"financeRate":{"$ref":"#/components/schemas/Decimal","description":"`finrte`. Finance charge rate."},"leadSource":{"type":"integer","format":"int32","description":"`ledsrc`. A foreign key into Sage's Lead Sources table.\n\nService inventory exposes the same kind of value (``invsrc``) as\n`sourceNumber` in v2, which collides with the GL transaction source and is\nrenamed to `leadSource` in v3. Install-specific, and the Lead Sources lookup\nis not exposed, so the code cannot currently be resolved to a name."},"clientType":{"type":"integer","format":"int16","description":"`clntyp`. Install-specific and **not an enum**. Query the client-type lookup\nrather than hardcoding values."},"status":{"type":"integer","format":"int16","description":"`status`. **Not a fixed enum.** Client statuses live in Sage's `clnsts`\nlookup and vary per install. No lookup endpoint models `clnsts` yet, so the\ncode cannot currently be resolved to a name; do not assume the\nOpen/Review/Dispute family that the document statuses use."},"beginningBalance":{"$ref":"#/components/schemas/Decimal","description":"`begbal`."},"endingBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`."},"noteText":{"type":"string","description":"`ntetxt`."},"statementEmail":{"type":"string","description":"`stmeml`. The address statements are sent to, distinct from `email1`."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`. A `0`/`1` flag despite the `is` prefix; Sage stores an integer and v3 does not reshape it."}},"description":"`the underlying record`, the receivable client (3-6 Receivable Clients).\n\nThere is no child collection on this entity, and no `lstupd` column, so\n`lastUpdated` filtering is not supported here.\n\n**Only `id` and `recordNumber` are guaranteed.** A query may narrow the row\nwith `select`, so every other field is absent unless it was asked for."},"CostCode":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Cost Code#\", declared `Numeric(11,3)`.\n\n**Published as an integer, which loses the three decimal places the column\nhas and every `costCode` foreign key carries.** See the model note."},"codeName":{"type":"string","description":"`cdenme`, which Sage labels \"Description\". Published as `codeName`."},"unitDesc":{"type":"string","description":"`untdsc`, Sage's \"Unit\".\n\nSpelled `unitDesc` here and `unitDescription` on `the underlying record`: one column, two\npublished names."},"division":{"$ref":"#/components/schemas/RecordNumber","description":"`divnum`, a foreign key to ``recnum``. Query the cost-division lookup\n(`CostDivision`).\n\nNote the spelling: this field is `division`, while the same column on\n`Subcontract` is published as `divisionNumber`."},"inactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag: `1` means the cost code is retired and should not be\noffered for new work.\n\nA `0`/`1` integer, not a boolean: Sage stores a smallint and this contract\npublishes what Sage stores. **Spelled `inactive` here and `isInactive` on\nclients, employees, equipment, jobs, parts, paygroups and vendors.**"}},"description":"`the underlying record`, the cost-code lookup (6-5 Cost Codes).\n\n**This is the table every \"query the cost-code lookup\" pointer in this contract\nresolves against**, including `JobCost.costCode`. Cost codes are defined per\ncompany, so they are never enumerated here; this endpoint is how a code becomes\na description.\n\n## `recordNumber` is an integer over a `Numeric(11,3)` column\n\n**This is a live defect, not a modelling choice.** Sage declares ``recnum``\nwith three decimal places, which is why every *foreign key* to it (`costCode`\non parts, subcontract lines and job costs) is a decimal string. This lookup\npublishes the same value as an integer, so it **truncates the very values its\nforeign keys can express**: cost codes `1.500` and `1.005` both read as `1`\nhere, and a caller filtering this entity by `1.500` cannot round-trip it.\n\n## Five of Sage's nine columns are not returned\n\nThis endpoint returns `recnum`, `cdenme`, `untdsc`, `divnum` and `inactv`. Sage\nalso carries `cmpcde` and `cmpcd2` (workers' comp codes, a foreign key to\n``recnum``), `maxwge` (wage max), `dptnum` (department) and `ntetxt`.\n\n**Note the spelling of the inactive flag on this entity.** It is published here\nas `inactive`, while the same Sage column is published as `isInactive` on\nclients, employees, equipment, jobs, parts, paygroups and vendors. One column,\ntwo names; filter and select by the name this entity publishes."},"CostDivision":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Division#\". The value `CostCode.division` and `Subcontract.divisionNumber` carry."},"divisionName":{"type":"string","description":"`divnme`, which Sage labels \"Description\". Published as `divisionName`."}},"description":"`the underlying record`, the cost-division lookup (6-5 Cost Divisions).\n\nCost divisions group cost codes (`CostCode.division` points here), and this is\nalso the lookup `Subcontract.divisionNumber` resolves against. Divisions are\ndefined per installation, so they are never enumerated in this contract.\n\nSage declares two columns on this table and both are returned."},"CostToComplete":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`), which for this entity is the job number."},"laborBurden":{"$ref":"#/components/schemas/Decimal","description":"The labor burden rate applied to the projection (`lbrbur`), a percentage.\n\nA JSON string like every other decimal, not a number."},"editDate":{"type":"string","format":"date","description":"When the projection was last edited (`edtdte`).\n\nA date, not an instant. This is also the column `cost-to-complete` supports\n`lastUpdated` incremental sync on, one of only seven entities that support\nit at all."},"userName":{"type":"string","description":"The Sage user who last edited the projection (`usrnme`)."},"noteText":{"type":"string","description":"Record note (`ntetxt`)."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CostToCompleteLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a job's cost-to-complete projection, as read.\n\nOne per job: Sage keys `the underlying record` on `recnum` and that record number is the job\nnumber (``recnum``), which is why there is no separate job field here.\n\nOnly `id` and `recordNumber` are required. Every other field is optional\nbecause a read may restrict the response with `select`, so its absence means\n\"not asked for\", not \"not set\"."},"CostToCompleteLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The parent record's record number (`recnum`), which is the job number.\nRepeated on every line; see the note above."},"phaseNumber":{"type":"integer","format":"int64","description":"The job phase this line projects (`phsnum`), a foreign key to the job-phase\nlookup. It is ``phsnum``, not ``recnum``: that column is the job\nnumber."},"lineNumber":{"type":"integer","format":"int32","description":"Position within the phase (`linnum`)."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Not an enum; query the cost-code lookup."},"totalBudget":{"$ref":"#/components/schemas/Decimal","description":"The budget for this cost code (`bdgttl`), carried over from the job budget.\n\n**Renamed from v2's `budgetTotal`.** It is the same concept as\n`BudgetLine.totalBudget` (``ttlbdg``), which v2 spelled differently on\neach of the two entities; v3 spells both `totalBudget`."},"costToDate":{"$ref":"#/components/schemas/Decimal","description":"Cost posted against this cost code so far (`cstdte`)."},"percentComplete":{"$ref":"#/components/schemas/Decimal","description":"Percent complete as computed from cost (`pctcst`), that is `costToDate`\nagainst `totalBudget`.\n\n**Renamed from v2's `percentCost`.** Sage's own data dictionary labels\n``pctcst`` \"% Completed\", and the structurally identical column on the\nhours-to-complete line (``pctcmp``, also \"% Completed\") is already\nexposed as `percentComplete`.\n\nDistinct from `actualComplete` below: this one is derived from money, that\none is a human's judgement."},"actualComplete":{"$ref":"#/components/schemas/Decimal","description":"The actual percent complete a person entered (`actcmp`, Sage's \"% Actual\").\nSage drives `costToComplete` from this rather than from `percentComplete`.\n\nKeeps v2's spelling, which is already consistent across cost-to-complete and\nhours-to-complete."},"costToComplete":{"$ref":"#/components/schemas/Decimal","description":"Cost remaining to complete this cost code (`cstcmp`)."},"overUnder":{"$ref":"#/components/schemas/Decimal","description":"Projected variance against the budget (`ovrund`, Sage's \"Over/(Under)\")."},"userDefined1":{"type":"string","description":"Free text, per install (`usrdf1`)."},"noteText":{"type":"string","description":"Line note (`ntetxt`)."}},"description":"`the underlying record`, a cost-to-complete line, as read.\n\nOne row per phase + cost code, structurally parallel to the hours-to-complete\nline (`the underlying record`): a budget column, a to-date column, a computed percentage, the\nuser's actual percentage, the remaining amount and the over/under.\n\nAs on `the underlying record`, `recordNumber` is **not** unique to a line: Sage keys\n`the underlying record` on the parent's `recnum` (the job number), so line identity is\n`recordNumber` + `phaseNumber` + `lineNumber`.\n\nEvery decimal below is a JSON **string** at native scale, including the two\npercentages. The v2 spec declared them `\"type\": \"number\"` while the wire\ncarried a string."},"CostType":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Cost Type#\", `Numeric(1)`, so `1` to `9`, and no more."},"costTypeName":{"type":"string","description":"`typnme`.\n\n**Published as `costTypeName` here and as `typeName` on `the underlying record`**: one\ncolumn under two names across two lookup tables."}},"description":"`the underlying record`, the cost-type lookup (6-3 Cost Types).\n\n**This is the table every \"query the cost-type lookup\" pointer in this contract\nresolves against**, including `JobCost.costType` and `LedgerAccount.costType`.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract and `5` Other are Sage\npresets; **`6` to `9` are defined per company**, and Sage allows no more than\nnine: `recnum` is `Numeric(1)`, which is the schema enforcing the limit. All\nfour of the user range are in use at some installations, so `6`-`9` must never\nbe hardcoded. That is why this is a lookup endpoint and not an enum.\n\n**This API validates `costType` nowhere and defaults it to `1` on three write\npaths**, so a cost whose type is unknown is silently classified as Material.\n\nSage declares only `recnum` and `typnme` on this table and both are returned."},"CreditCardReconciliation":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). This row's own; the underlying record is not a child table."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lgract`, a foreign key to ``recnum``: the card's liability account.\n\n**This is also the join to `CardIssuer`**, which Sage keys on the same ledger\naccount: one issuer row per card account. `CardIssuer.recordNumber` equals\nthis field."},"description":{"type":"string","description":"`dscrpt`."},"saveDate":{"type":"string","format":"date","description":"`svedte`, Sage's \"Date\": when the reconciliation was saved. A date, not an\ninstant, and a business date a user chooses rather than a modification stamp.\nSee `BankReconciliation.saveDate`. This entity offers no watermark."},"statementDate":{"type":"string","format":"date","description":"`stmdte`, Sage's \"Statement Ending Date\"."},"cutoffDate":{"type":"string","format":"date","description":"`cutdte`, Sage's \"Cutoff Date\": transactions after this date are excluded."},"currentBalance":{"$ref":"#/components/schemas/Decimal","description":"`curbal`, `Numeric(9,2)`. The ledger's balance for the account. JSON string, native scale."},"statementBeginBalance":{"$ref":"#/components/schemas/Decimal","description":"`stmbeg`, Sage's \"Statement Beginning Balance\"."},"statementEndBalance":{"$ref":"#/components/schemas/Decimal","description":"`stmend`, Sage's \"Statement Ending Balance\". The figure `lines` has to reconcile to."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreditCardReconciliationLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a credit card reconciliation, as read.\n\nOne saved reconciliation of one card account against one statement: Sage menu\n4-7-5. **The header is `the underlying record` column for column** (same account, same three\ndates, same three balances), and the models are still separate, because the\nlines differ and a shared header would only make the pair look interchangeable.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a request may narrow the response with `select`."},"CreditCardReconciliationLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"idRef":{"type":"string","description":"The parent reconciliation's `id` (`_idref`), the column the child fetch joins on."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``. See the model doc: not this row's own number."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"lineType":{"type":"integer","format":"int16","description":"`lintyp`, Sage `Numeric(1)`.\n\n**Deliberately not an enum**, on the same grounds as\n`BankReconciliationLine.lineType`: the value set is not documented, so the\nmeanings have to be read off the install."},"subAccount":{"type":"integer","format":"int64","description":"`subact`, a foreign key to ``recnum``.\n\n**Present here and absent on `the underlying record`.** Spelled `subAccount`, which is the\nv3 name for `subact` on every entity that carries it."},"ledgerRecord":{"$ref":"#/components/schemas/RecordNumber","description":"`lgrrcd`, a foreign key to ``recnum``: the ledger transaction this line\nclears.\n\nThe column is `lgrrcd`, not the `lgrrec` most other tables use, and it is not\na typo. See `BankReconciliationLine.ledgerRecord`, which carries the full\nnote."},"isPriorYear":{"type":"integer","format":"int16","description":"`ispryr`, Sage's \"Is Prior Year\". A `Yes/No` column arriving as `0` or `1`;\nan integer rather than a boolean despite the `is` prefix."},"transactionNumber":{"type":"string","description":"`trnnum`, Sage's \"Transaction#\", `Character(20)`: a document number, not an integer."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. A date, not an instant."},"payee":{"type":"string","description":"`payee_`. The trailing underscore is Sage escaping a reserved word; it never reaches the wire."},"creditAmount":{"$ref":"#/components/schemas/Decimal","description":"`crdamt`, Sage's \"Credit Amount\", `Numeric(9,2)`. JSON string, native scale.\n\nA refund or payment against the card. Paired with `chargeAmount`: Sage puts\nthe two directions in two columns here rather than signing one, so a caller\ncomputing a line's effect has to read both."},"chargeAmount":{"$ref":"#/components/schemas/Decimal","description":"`chgamt`, Sage's \"Charge Amount\". The spend side of the pair; see `creditAmount`."},"transactionHash":{"type":"string","description":"`trnhsh`, `Character(64)`: the hash of the imported transaction this line was\nmatched to.\n\n**The join to `BankingTransaction`**, which Sage keys on the same\n64-character `trnhsh` rather than on a record number. Empty on a line\nreconciled by hand."}},"description":"`the underlying record`, a credit card reconciliation line, as read.\n\nOne item on a card statement. It is `the underlying record` plus two columns, and the two\ncolumns are why this is a separate model rather than a shared one: a card line\nsplits its value into a **credit** and a **charge** where a bank line carries\none signed `amount`, and it carries a subaccount where a bank line does not.\n\n`recordNumber` is the **parent's** record number: Sage keys the underlying record on `recnum`\ntargeting ``recnum``, as every line table in the product does. Redundant\nwhen read nested under the parent, which is the only way it can be read, but the\ncolumn is in the child `SELECT` and therefore on the wire."},"DailyPayroll":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"payDate":{"type":"string","format":"date","description":"`paydte`, the day the hours were worked. A date, not an instant."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`, the line's position within the day's entry."},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`empnum`, a foreign key to ``recnum``: the `Employee` entity's `recordNumber`."},"description":{"type":"string","description":"`dscrpt`. On the equivalent `the underlying record` row this carries the earning code; here it is free text."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``: the `Job` entity. `the underlying record` is the job, not the job cost."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, a foreign key to ``phsnum``. Not a `recnum`, which is why it is a plain `int64`."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, and **deliberately not an enum**: query the cost-code\nlookup instead. Encoded as a decimal string because the column is one: cost\ncodes are conventionally written `1.2.030`-style and the fractional digits\nare significant."},"payType":{"type":"integer","format":"int16","description":"`paytyp`, the earning pay type: `1-Regular`, `2-Overtime`, `3-Premium`,\n`4-Sick`, `5-Vacation`, `6-Holiday`, `7-Piece`, `8-Per Diem`,\n`9-Miscellaneous`, verified against Sage's own help. There is no Bonus type;\nbonuses are `9`.\n\nIt is typed as an integer rather than an enum because the shared enum the\nread entities would draw this family from does not exist yet."},"paygroup":{"$ref":"#/components/schemas/RecordNumber","description":"`paygrp`, a foreign key to ``recnum``. See `Employee.paygroup` for why the name carries no `Number` suffix."},"payRate":{"$ref":"#/components/schemas/Decimal","description":"`payrte`, the rate for these hours."},"payHours":{"$ref":"#/components/schemas/Decimal","description":"`payhrs`, the hours. `TimecardLine` calls the same quantity `hoursWorked` (`hrswrk`), a different column on a different table."},"compCode":{"$ref":"#/components/schemas/RecordNumber","description":"`cmpcde`, a foreign key to ``recnum``: the `WorkersComp` entity. ``wrkcmp`` is the employee-level default."},"department":{"type":"integer","format":"int64","description":"`dptmnt`. A record number into the department table; `0` when unset."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a daily payroll line (Sage 100 Contractor **5-5-1**), as read.\n\nHours entered as they are worked, day by day, before a check exists: one row\nper employee per job per cost code per day. It is step 1 of Sage's native\npayroll flow, the input Sage later gathers into `the underlying record` + `the underlying record` when\npayroll is computed.\n\n**OPDC never writes this table, by design.** The `POST /payroll` endpoint\ncollapses Sage's first five payroll steps: the provider has already computed\nand cut the checks, so the API imports the finished result and posts `the underlying record`,\n`the underlying record`, `the underlying record`, `the underlying record` and the journal in one transaction. Daily payroll\nis upstream of all of that. So on an OPDC-posted company this entity returns\nonly what was entered natively in Sage, and **an empty result is the expected\nreading, not a fault**. Sage gives it its own GL source code:\n`18-Daily Payroll`, distinct from `16-Payroll`.\n\n**This model has no `recordNumber`, and the reason is worth checking before\nanyone \"fixes\" it.** This table has no `recnum` column at all (unlike every\nother payroll table), so there is nothing for the query to alias and a\n`recordNumber` field here would name a column that cannot be produced. The\nrow's identity on the wire is `id`, and `payDate` + `lineNumber` is its\nnatural key.\n\n`the underlying record` registers no child relation, so no `children` argument.\n\n**Only `id` is guaranteed**; `select` may narrow the rest."},"Decimal":{"type":"string","pattern":"^-?[0-9]+(\\.[0-9]+)?$","description":"A decimal quantity or amount.\n\nEncoded as a JSON **string** at its native scale in both directions, never as\na JSON number, because a JavaScript client rounds a value like `36.303` through\na float, and forcing a fixed scale on the way out is a lossy transformation\ndressed up as formatting. Values are returned at the scale Sage stores them at\nand are not padded or truncated to two places.","examples":["1500.00"]},"Employee":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): the employee number a human reads in\nSage.\n\n**This is the value the rest of payroll carries as `employeeNumber`**:\n``empnum``, ``empnum``, and the write contract's\n`PayrollCheckInput.employeeNumber`, which is resolved against this table and\nfails the whole batch when it matches no employee. v3 spells this value\n`employeeNumber` on every entity that carries it."},"lastName":{"type":"string","description":"`lstnme`."},"firstName":{"type":"string","description":"`fstnme`."},"middleInitial":{"type":"string","description":"`midini`."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`.\n\n**Deliberately not modelled as an enum.** The meaning of the codes on this\nparticular table is not documented, and nothing in the API reads or writes\nit. In particular, do not assume the `1-Open`/`2-Review`/`3-Dispute` shape\nthat the AP and purchase-order statuses use: payroll and job statuses do\nnot participate in that family. Resolve the codes against Sage's own help\nbefore branching on them."},"addressLine1":{"type":"string","description":"`addrs1`. Home address. See the PII note on this model."},"addressLine2":{"type":"string","description":"`addrs2`."},"cityName":{"type":"string","description":"`ctynme`."},"stateCode":{"type":"string","description":"`state_`. Two-letter state or province code, as entered."},"zipCode":{"type":"string","description":"`zipcde`."},"phoneNumber":{"type":"string","description":"`phnnum`."},"cellPhone":{"type":"string","description":"`cllphn`."},"faxNumber":{"type":"string","description":"`faxnum`."},"homeNumber":{"type":"string","description":"`homnum`. The employee's home telephone number. See the PII note."},"email":{"type":"string","description":"`e_mail`. See the PII note."},"equipmentNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`eqpnum`, a foreign key to ``recnum`` (Equipment): the piece of\nequipment assigned to this employee. Reads `0` when none is."},"birthDate":{"type":"string","format":"date","description":"`dtebth`, the date of birth. A date, not an instant.\n\n**The single most sensitive column on the model.** Read the PII note above\nbefore adding a caller to this entity."},"hireDate":{"type":"string","format":"date","description":"`dtehre`, the hire date. A date, not an instant."},"inactiveDate":{"type":"string","format":"date","description":"`dteina`, the date the employee was made inactive. Distinct from `isInactive`, which is the flag."},"lastRaise":{"type":"string","format":"date","description":"`lstrse`, the date of the last raise. Not a modification stamp; it says nothing about when the row changed."},"gender":{"type":"integer","format":"int16","description":"`gender`, Sage `Numeric(1)`.\n\nNot modelled as an enum: which integer means what is not established\nanywhere this API can verify. Query Sage's own screen if a caller needs it."},"positionNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`paypst`, a foreign key to ``recnum`` (the `PayrollPosition` entity).\n\nNamed `position` in v2. It is spelled `positionNumber` here because a field\ncarrying another table's record number ends in `Number` throughout v3. No\nother model publishes this column, so there is only ever one spelling of it."},"compCode":{"$ref":"#/components/schemas/RecordNumber","description":"`wrkcmp`, a foreign key to ``recnum`` (the `WorkersComp` entity): the\nemployee's default workers' compensation code.\n\n**Published as `compCode`, without a `Number` suffix**, on the precedent of\n`costCode`: ``recnum`` is a foreign key too and ships as `costCode` on\nevery model that carries it. \"Comp code\" is Sage's own term for this value.\n\nNote the column: `TimecardLine` and `DailyPayroll` reach the same lookup\nthrough `cmpcde`, a **different column** published under this same field\nname."},"taxState":{"type":"string","description":"`taxste`. The state whose withholding tables apply; copied onto ``taxste`` per check."},"payPeriod":{"type":"integer","format":"int16","description":"`payprd`, the pay-period **frequency code** (weekly, bi-weekly, semi-monthly,\nmonthly): an integer code, not a date.\n\n**``payprd`` is the same column name on a different table holding the\nperiod END DATE**, published there as `periodEnd: plainDate`. One column\nname, two types, two meanings. Reading this field as a date is the obvious\nmistake and it is why both fields are annotated.\n\nThe frequency codes themselves are not enumerated by this API, so this is\nnot typed as an enum."},"paygroup":{"$ref":"#/components/schemas/RecordNumber","description":"`paygrp`, a foreign key to ``recnum`` (the `Paygroup` entity).\n\nPublished as `paygroup`, without a `Number` suffix, consistently with every\nother model in the API that carries this column."},"regularRate":{"$ref":"#/components/schemas/Decimal","description":"`payrt1`, the master regular rate. See the PII note.\n\nOne of the three columns a posted payroll batch is reconciled against: an\nearning of pay type `1-Regular` whose rate differs from this by more than\nthe batch's `rateTolerance` fails the whole batch."},"overtimeRate":{"$ref":"#/components/schemas/Decimal","description":"`payrt2`, the master overtime rate. Reconciled against pay type `2-Overtime`."},"premiumRate":{"$ref":"#/components/schemas/Decimal","description":"`payrt3`, the master premium (double-time) rate. Reconciled against pay type\n`3-Premium`.\n\n**Rate parity stops here.** Pay types 4 through 9 have no master rate column\nto check against (`the underlying record` has exactly these three), so earnings of those\ntypes are not rate-checked at all."},"salaryAmount":{"$ref":"#/components/schemas/Decimal","description":"`salary`, the salaried employee's per-period salary. See the PII note.\n\n**Named `salary` in v2, published as `salaryAmount` here.** `the underlying record`\npublishes the same column as `salaryAmount` already, so the two entities now\nagree; a money field carries the `Amount` suffix throughout v3."},"userDefined1":{"type":"string","description":"`usrdf1`."},"userDefined2":{"type":"string","description":"`usrdf2`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag Sage stores as a smallint.\n\nNot a `boolean`: the wire carries `0`/`1` and the read path does not\nconvert. Every `inactv` flag in this API is published the same way."}},"description":"`the underlying record`, the employee master (Sage 100 Contractor **5-2-1**), as read.\n\nThe setup table the whole payroll module hangs off: every `the underlying record` check, every\n`the underlying record` daily-payroll row and the batch contract's `employeeNumber` all point\nat this table's `recnum`, and `payrt1`/`payrt2`/`payrt3` here are the master\nrates a posted payroll batch is reconciled against before it will be accepted.\n\n**This model publishes a real PII surface, and there is no field-level access\ncontrol behind it.** `birthDate`, `salaryAmount`, the three pay rates,\n`email`, `homeNumber`, `cellPhone` and the home address are returned to any\ncaller holding a valid `x-api-key` for the tenant; `select` is a convenience\nfor the caller, not a permission boundary, and the API key that reads a part\nprice reads an employee's date of birth and wage with the same credential.\nThis is unchanged from v2. Sage's own SSN column is not exposed by this API;\nnothing else on the table is held back.\n\n**This table's name is lower case (`the underlying record`)**, while the other eleven\npayroll tables are upper case. Sage's SQL Server collation makes that\nharmless; it matters only to someone matching table names as strings.\n\n`the underlying record` registers no child relation, so there is no `children` argument on\nthis entity; quarter-to-date accumulators live in `EmployeeEarnings`\n(`the underlying record`) and are read as their own entity. There is no `lstupd` column\neither, so a `lastUpdated` filter here is the 501, not a supported incremental\nread.\n\n**Only `id` and `recordNumber` are guaranteed.** Every other field is optional\nbecause a request may narrow the row with `select`."},"EmployeeEarnings":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"idRef":{"type":"string","description":"The parent employee's `id` (`_idref`), the UUID side of the same link\n`employeeNumber` carries as an integer. In the `SELECT`, and therefore on the\nwire, even though `the underlying record` declares no child relation that would return this\nrow nested."},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the employee's record number, not this row's.** A foreign key to\n``recnum``, the `Employee` entity's `recordNumber`. See the model note."},"calculationNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clcnum`, a foreign key to ``recnum``: the `PayrollCalculation` entity's\n`recordNumber`.\n\n**Named `calculationNum` in v2, published as `calculationNumber` here.** v2\npublished this one target table under two field names (`calculation` on\n`the underlying record` and `calculationNum` here), and v3 spells both `calculationNumber`.\n`Benefit.calculationNumber` (``dednum``) is spelled the same for the\nsame target."},"active":{"type":"integer","format":"int16","description":"`active`, a 0/1 flag stored as a smallint: whether this calculation applies\nto this employee.\n\nNot a `boolean`: the wire carries `0`/`1` and the read path does not\nconvert. Note that `the underlying record` publishes a column of the same name as\n`billingActive` over unrelated data; the two are not the same value."},"deductionRate":{"$ref":"#/components/schemas/Decimal","description":"`dedrte`, this employee's rate for the calculation, overriding `PayrollCalculation.defaultRate`."},"deductionMax":{"$ref":"#/components/schemas/Decimal","description":"`dedmax`, this employee's cap, overriding `PayrollCalculation.defaultMax`."},"firstQuarter":{"$ref":"#/components/schemas/Decimal","description":"`fstqtr`, the first-quarter total. Sage-maintained; see the model note."},"secondQuarter":{"$ref":"#/components/schemas/Decimal","description":"`scdqtr`, the second-quarter total."},"thirdQuarter":{"$ref":"#/components/schemas/Decimal","description":"`thdqtr`, the third-quarter total."},"fourthQuarter":{"$ref":"#/components/schemas/Decimal","description":"`fthqtr`, the fourth-quarter total."}},"description":"`the underlying record`, an employee's quarter-to-date accumulator for one payroll\ncalculation, as read.\n\nOne row per **(employee × calculation)**: the employee's rate and cap override\nfor that calculation, plus the four quarterly totals withheld or accrued under\nit. The entity name is a loose fit, kept from v2: the row is as much a\ndeduction setting as an earning.\n\n**This model has no `recordNumber`, and the reason is not an omission.**\n``recnum`` holds the **employee's** record number (this is a Sage\nsub-table keyed on its parent, the same shape as `the underlying record`/`the underlying record`/`the underlying record`),\nand it is therefore published as `employeeNumber` rather than `recordNumber`.\nA `recordNumber` field here would name a column the query cannot produce; the\nrow's own identity is `id`.\n\n**The quarterly totals are Sage's, not OPDC's.** OPDC does not maintain\nquarter- or year-to-date accumulators, and nothing it writes touches `the underlying record`.\nSo on a company whose payroll is posted through OPDC these four columns\nreflect only what was run natively in Sage. Do not reconcile a provider's\nquarterly filings against them.\n\n**Only `id` is guaranteed**; `select` may narrow the rest."},"Equipment":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"equipmentName":{"type":"string","description":"`eqpnme`."},"shortName":{"type":"string","description":"`shtnme`, an abbreviated name for reports and pick lists."},"serialNumber":{"type":"string","description":"`srlnum`. Spelled `sernum` on `the underlying record` and `the underlying record`: one concept, two Sage column names."},"licenseNumber":{"type":"string","description":"`licnum`, the vehicle licence or registration. Free text."},"lender":{"type":"integer","format":"int64","description":"`lender`, the finance company behind the unit.\n\nA foreign key, and **its target is unverified**. ``recnum`` (the vendor\nmaster) is the obvious candidate, since a lender is paid, but no lender\ntable is exposed by this API and the join is not confirmed. Deliberately not\ntyped as a record number until it is; do not assume `@minValue(1)`."},"purchaseDate":{"type":"string","format":"date","description":"`pchdte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"department":{"type":"integer","format":"int64","description":"`depmnt`, the department the unit is assigned to.\n\nA foreign key whose target table is **not exposed by this API**, so the code\ncannot be resolved to a department name here today."},"userDefined1":{"type":"string","description":"`usrdf1`."},"userDefined2":{"type":"string","description":"`usrdf2`."},"operatedCost":{"$ref":"#/components/schemas/Decimal","description":"`oprcst`, the cost rate while the unit is working. JSON string, native scale."},"standbyCost":{"$ref":"#/components/schemas/Decimal","description":"`stbcst`, the rate while it is on standby: allocated to a job but not running."},"idleCost":{"$ref":"#/components/schemas/Decimal","description":"`idlcst`, the rate while it is idle."},"totalHours":{"$ref":"#/components/schemas/Decimal","description":"`ttlhrs`, lifetime hours. An accumulator, not a period figure."},"totalMiles":{"$ref":"#/components/schemas/Decimal","description":"`ttlmls`, lifetime miles."},"equipmentType":{"type":"integer","format":"int16","description":"`eqptyp`, a foreign key to ``recnum``.\n\nInstall-specific, so **deliberately not an enum**: query the equipment-type\nlookup, which is modelled as `EquipmentType`. This is the one coded field in\nthe equipment group whose lookup table is definitely present."},"status":{"type":"integer","format":"int16","description":"`status`.\n\n**Deliberately not an enum**, because the members for this table are not\nestablished. The status families verified elsewhere in this API (AP\ninvoices, AR invoices, purchase orders, service receivables, inventory\nallocations, jobs, ledger transactions, payroll records) do not include\nequipment. Do **not** assume the `1-Open` / `2-Review` / `3-Dispute` prefix\nseveral of those families share: the sequences diverge from position 4\nonward. Resolve the values against Sage's help before relying on them."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. The created stamp `createdSince` filters on."},"userName":{"type":"string","description":"`usrnme`, the Sage user who entered the record."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag stored as a smallint.\n\n**The `is` prefix does not make this a boolean.** The wire carries `0` or\n`1`, not `false`/`true`, exactly as on `Part.isInactive`; no conversion is\napplied on the way out. Compare against the integers."}},"description":"`the underlying record`, the company's equipment register (Sage menu 8), as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\n**This is the company's own equipment, not the customer's.** `the underlying record` is the\nunit at a customer site that a service call is about, and it is keyed by client\nrather than by record number. The two tables share three field names and spell\nthe serial-number column differently (`srlnum` here, `sernum` there); see\n`ServiceEquipment`.\n\nThe three costs (`operatedCost`, `standbyCost`, `idleCost`) are the rates Sage\ncharges equipment time out at, and `the underlying record`/`the underlying record` are where the resulting\ncosts and revenue land. Sage's transaction sources reserve five codes for this\nmodule: `19` Eqp Alloc (menu 8-4), `20` Eqp Payment (8-5), `21` Eqp Deprec\n(8-6), `32` Eqp Cost (8-2) and `33` Eqp Rev (8-7). **This API writes none of\nthem**; there is no equipment posting endpoint, so every row was entered in\nSage directly.\n\n`entdte` is a created stamp, so `createdSince` is honoured on this entity.\n`lastUpdated` is a 501: Sage keeps no modification stamp on the table."},"EquipmentCost":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"equipmentNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`eqpnum`, a foreign key to ``recnum``: the company's own equipment register, not `the underlying record`."},"transactionNumber":{"type":"string","description":"`trnnum`, the transaction number. A string, as on `the underlying record`."},"description":{"type":"string","description":"`dscrpt`."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. The created stamp `createdSince` filters on."},"period":{"type":"integer","format":"int16","description":"The period the transaction posted into.\n\n**The Sage column is `actprd`, not `actper`.** Every other table on the read\nsurface that carries a posting period spells it `actper` (`the underlying record`,\n`the underlying record`, `the underlying record`), and the two equipment transaction tables spell it\n`actprd`. One published name (`period`) over two Sage column names, which is\nthe right way round; it matters only to anyone writing SQL against the\ndatabase directly."},"sourceNumber":{"type":"integer","format":"int16","description":"`srcnum`, the GL transaction source, a foreign key to ``recnum``.\n\n**A real GL source, unlike `ServiceInventory.sourceNumber`**, which shares\nthis published name while pointing at the Lead Sources table. Here the name\nmeans what it means everywhere else. `32` is \"Eqp Cost\" (menu 8-2), the value\nSage stamps on rows in this table.\n\n**Deliberately not an enum**: sources `1`-`33` are system-defined and safe to\nname, `34` and above are install-specific. Query the transaction-source\nlookup."},"status":{"type":"integer","format":"int16","description":"`status`.\n\n**Deliberately not an enum**, because the members for this table are not\nestablished. Do **not** assume the `1-Open` / `2-Review` / `3-Dispute` prefix\nthat several other Sage status families share: the sequences diverge from\nposition 4 onward. Resolve the values against Sage's help before relying on\nthem."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract and `5` Other are\nSage presets; **`6` to `9` are defined per company**, so this is deliberately\nnot an enum. Query the cost-type lookup. Note that `3` is itself \"Equipment\",\nwhich will be the common value here and is a cost classification rather than\na reference to this module."},"costAmount":{"$ref":"#/components/schemas/Decimal","description":"`cstamt`. JSON string, native scale, as every decimal here is."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this cost posted, or `0` when no\njournal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason, and kept as\n`ledgerRecord` because all fourteen models carrying `lgrrec` agree on the\nspelling."},"year":{"type":"integer","format":"int16","description":"`postyr`."}},"description":"`the underlying record`, an equipment cost transaction (Sage menu 8-2), as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\nThe cost side of the equipment ledger; `the underlying record` is the revenue side, and the\ntwo models are near-identical in shape. Sage's transaction source `32` is\n\"Eqp Cost\", named for menu 8-2, and this is the table it stamps. **Nothing in\nthis API writes it** (there is no equipment posting endpoint), so every row a\ncaller reads was entered in Sage directly.\n\n`entdte` is a created stamp, so `createdSince` is honoured on this entity.\n`lastUpdated` is a 501: Sage keeps no modification stamp on the table. Note the\ncontrast with `EquipmentRevenue`, which exposes no `entdte` and therefore\nhonours neither filter."},"EquipmentRevenue":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"equipmentNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`eqpnum`, a foreign key to ``recnum``: the company's own equipment register, not `the underlying record`."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``: the job the equipment time was charged to."},"transactionNumber":{"type":"string","description":"`trnnum`, the transaction number. A string, as on `the underlying record`."},"description":{"type":"string","description":"`dscrpt`."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"period":{"type":"integer","format":"int16","description":"The period the transaction posted into.\n\n**The Sage column is `actprd`, not `actper`**, as on `the underlying record` and unlike\nevery other table on the read surface. See `EquipmentCost.period`."},"revenueAmount":{"$ref":"#/components/schemas/Decimal","description":"`rvnamt`. JSON string, native scale, as every decimal here is."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this revenue posted, or `0` when\nno journal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason, and kept as\n`ledgerRecord` because all fourteen models carrying `lgrrec` agree on the\nspelling."},"year":{"type":"integer","format":"int16","description":"`postyr`."}},"description":"`the underlying record`, an equipment revenue transaction (Sage menu 8-7), as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\nThe revenue side of `EquipmentCost`: equipment time charged out to a job. It\ncarries `jobnum`, which the cost table does not, and that is the whole\ndifference in shape. Sage's transaction source `33` is \"Eqp Rev\", named for\nmenu 8-7, but note that no `srcnum` column is exposed on this entity, so a\ncaller cannot see the source stamped on a row here. **Nothing in this API\nwrites the table**; every row a caller reads was entered in Sage directly.\n\n**Neither change-stamp filter works on this entity**: `lastUpdated` and\n`createdSince` both answer 501. `the underlying record` does carry a created stamp in Sage,\nbut the column is not among those this API selects, so there is nothing to\nfilter on until it is added."},"EquipmentType":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). The value `Equipment.equipmentType` carries."},"typeName":{"type":"string","description":"`typnme`, the name a human reads. The whole reason to query this table."}},"description":"`the underlying record`, an equipment type, as read.\n\nThe lookup behind `Equipment.equipmentType`: without it, ``eqptyp`` is an\ninteger a caller has no way to resolve to a name. Equipment types vary from one\ncompany to the next, which is why Sage keeps them in this table rather than\nfixing them as constants. Query it rather than hardcoding codes.\n\nStructurally identical to `ServiceType` (`the underlying record`), including the column names,\nand unlike that one the join is not in doubt: `Equipment.equipmentType` is\nnamed for this table.\n\nNo change stamp, as with every lookup table: `lastUpdated` and `createdSince`\nboth answer 501."},"ErrorCode":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN","SERVICE_DISABLED","MAINTENANCE_LOCK","LOCK_CONFLICT","REQUEST_INVALID","HEADER_INVALID","CHANGE_STAMP_UNSUPPORTED","UPSTREAM_TIMEOUT","SCRIPT_NOT_FOUND","RESPONSE_INVALID","INTERNAL_ERROR","IDEMPOTENCY_KEY_REUSED","IDEMPOTENCY_KEY_IN_FLIGHT","IDEMPOTENCY_KEY_INVALID","PERIOD_CLOSED","PERIOD_UNRESOLVED","LEDGER_SETUP_MISSING","JOURNAL_UNBALANCED","JOURNAL_EMPTY","JOURNAL_LINE_INVALID","DUPLICATE_TRANSACTION","LEDGER_ACCOUNT_UNKNOWN","SERVICE_INVOICE_NOT_POSTABLE","PAYROLL_BATCH_EMPTY","PAYROLL_CHECK_INVALID","PAYROLL_EARNING_UNSUPPORTED","PAYROLL_DEDUCTION_INVALID","RECORD_NOT_FOUND","FIELD_REQUIRED","FIELD_INVALID","NOT_IMPLEMENTED","SAGE_SCHEMA_MISMATCH","SAGE_ERROR","SCRIPT_FAILED"],"description":"**The error taxonomy. There is exactly one, and this is it.**\n\nThe same codes are carried in the `X-Error-Code` response header on both API\nversions, so a client that already branches on that header sees no new\nvocabulary here.\n\n**Codes are append-only.** A code's spelling and meaning never change once\npublished, and a failure mode that stops existing keeps its code reserved\nrather than having it reused. It is the one part of a failure you may safely\nbranch on in code rather than by pattern-matching prose.\n\nA code maps to `Problem.type` as `https://twinn.engineering/errors/` plus the\ncode lowercased with underscores replaced by hyphens: `PERIOD_CLOSED` becomes\n`https://twinn.engineering/errors/period-closed`."},"FilterNode":{"type":"object","properties":{"and":{"type":"array","items":{"$ref":"#/components/schemas/FilterNode"},"description":"All children must match."},"or":{"type":"array","items":{"$ref":"#/components/schemas/FilterNode"},"description":"At least one child must match."},"match":{"$ref":"#/components/schemas/MatchFilter","description":"Leaf comparison."}},"description":"A recursive boolean expression. Exactly one of `and`, `or` or `match` is set."},"FilterOperator":{"type":"string","enum":["=","!=",">","<",">=","<=","LIKE","NOT LIKE","IN","inPairs"],"x-enum-varnames":["Eq","Ne","Gt","Lt","Gte","Lte","Like","NotLike","In","InPairs"]},"HistoricalAverageCost":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"idRef":{"type":"string","description":"`_idref`, the part row's `_idnum`.\n\nNot a child-collection pointer: `the underlying record` is a top-level entity and there is\nno request that returns it nested under its part. The column is in the\n`SELECT` and therefore on the wire."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). Unlike `InventorySerial`, this table's\n`recnum` is the row's own key, not a repeat of the part's."},"fiscalYear":{"type":"integer","format":"int16","description":"`fsclyr`, the fiscal year this average applies to."},"averageCost":{"$ref":"#/components/schemas/Decimal","description":"`avgcst`, the part's average cost for that year."},"validDate":{"type":"string","format":"date","description":"`valdte`, the date the figure is valid as at. Date, not an instant."}},"description":"`the underlying record`, a part's average cost as at the end of a fiscal year.\n\nSix columns. It is the historical counterpart to `Part.averageCost`, which\nholds only the current figure, so this is the only way to ask what a part cost\nin a prior year.\n\n**This API never writes it.** The rows are Sage's, and appear to be rolled at\nyear end, though that timing is inferred from the columns rather than confirmed.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a query may narrow the row with `select`."},"HoursToComplete":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`), which for this entity is the job number."},"editDate":{"type":"string","format":"date","description":"When the projection was last edited (`edtdte`).\n\nA date, not an instant: v2 returned `2026-01-15T00:00:00Z` for a Sage `Date`\ncolumn. This is the column `lastUpdated` filters on for this entity; see the\nmodel note."},"userName":{"type":"string","description":"The Sage user who last edited the projection (`usrnme`)."},"noteText":{"type":"string","description":"Record note (`ntetxt`)."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/HoursToCompleteLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`.\n\nChildren ignore `select` and are unpaginated and unfilterable, behaviour\nthat is contract and that v2 never stated."}},"description":"`the underlying record`, a job's hours-to-complete projection, as read.\n\nOne per job: Sage keys `the underlying record` on `recnum`, and there is no job column on the\ntable to key it any other way: the record number *is* the job number\n(``recnum``), exactly as on `the underlying record`, `the underlying record` and `the underlying record`.\n\n**The route is `/hours-to-completes`.** The plural is mechanical rather than\ngrammatical, so that the rule needs no looking up.\n\n**One of only seven entities where `lastUpdated` is honoured rather than\nanswered with a 501**: ``edtdte`` is a real edit stamp, alongside\n`the underlying record`, `the underlying record`, `the underlying record`, `the underlying record`, `the underlying record` and `the underlying record`. Note that the\nstamp is published as `editDate`, not as a field called `lastUpdated`: the\nfilter and the column keep their own names.\n\nOnly `id` and `recordNumber` are required. Every other field is optional\nbecause a v3 read may restrict the response with `select`, so an absent field\nmeans \"not asked for\", not \"not set\"."},"HoursToCompleteLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The parent record's record number (`recnum`), which is the job number.\nRepeated on every line; see the note above."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `HoursToComplete`, and the column the child\nfetch joins on.\n\nRedundant when read nested under the parent, which is the only way it can be\nread, but the child `SELECT` is built from the full column map and ignores\n`select`, so it is on the wire regardless."},"phaseNumber":{"type":"integer","format":"int64","description":"The job phase (`phsnum`), a foreign key to the job-phase lookup."},"lineNumber":{"type":"integer","format":"int32","description":"Position within the phase (`linnum`)."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"budgetHours":{"$ref":"#/components/schemas/Decimal","description":"Budgeted hours for this cost code (`bdghrs`), carried over from the job budget's `hoursBudget`."},"hoursToDate":{"$ref":"#/components/schemas/Decimal","description":"Hours posted against this cost code so far (`hrsdte`)."},"percentComplete":{"$ref":"#/components/schemas/Decimal","description":"Percent complete as computed from hours (`pctcmp`, Sage's \"% Completed\"),\nthat is `hoursToDate` against `budgetHours`.\n\nDistinct from `actualComplete`: this one is derived, that one is a person's\njudgement, and Sage drives `hoursToComplete` from the judgement."},"actualComplete":{"$ref":"#/components/schemas/Decimal","description":"The actual percent complete a person entered (`actcmp`, Sage's \"% Actual\"),\nas opposed to the figure `percentComplete` derives from hours."},"hoursToComplete":{"$ref":"#/components/schemas/Decimal","description":"Hours remaining to complete this cost code (`hrscmp`)."},"overUnder":{"$ref":"#/components/schemas/Decimal","description":"Projected variance against the budget (`ovrund`, Sage's \"Over/(Under)\"), in\nhours; every measured column on this line is hours.\n\nA cost-to-complete line publishes the same Sage column under the same name in\nmoney; each table measures the concept in its own unit."},"userDefined1":{"type":"string","description":"Free text, per install (`usrdf1`)."},"noteText":{"type":"string","description":"Line note (`ntetxt`)."}},"description":"`the underlying record`, an hours-to-complete line, as read.\n\nOne row per phase + cost code. The same skeleton as a cost-to-complete line (a\nbudget column, a to-date column, a computed percentage, the user's actual\npercentage, the remainder and the over/under), measured in hours instead of\nmoney: `bdghrs` for `bdgttl`, `hrsdte` for `cstdte`, `hrscmp` for `cstcmp`.\n\nAs on `the underlying record` and `the underlying record`, `recordNumber` is **not** unique to a line: Sage\nkeys `the underlying record` on the parent's `recnum`, which is the job number, so line\nidentity is `recordNumber` + `phaseNumber` + `lineNumber`.\n\nEvery decimal below is a JSON **string** at native scale, including the two\npercentages. The v2 spec declared them `\"type\": \"number\"` while the wire carried\na string."},"InventoryAllocation":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"transactionNumber":{"type":"string","description":"`trnnum`, the caller-supplied ticket number. A string, not an integer, and\nnot generated; the write endpoint takes it as `ticketNumber`."},"jobNumber":{"type":"integer","format":"int64","description":"`jobnum`, a foreign key to ``recnum``: the job, not the job cost\n(`the underlying record` is the job master; job cost is `the underlying record`).\n\nNot typed `RecordNumber` (`@minValue(1)`): a `LOCATION_TRANSFER` allocation\nhas no job and stores `0`. Only a `JOB_REQUISITION` requires one, and only\nthat purpose produces a `the underlying record` row (see `AllocationPurpose`)."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, a foreign key to ``phsnum``. `0` when unset."},"addressLine1":{"type":"string","description":"`addrs1`. The four address columns and `cityName` / `stateCode` / `zipCode`\nare **copied from the job** at insert, not supplied by the caller, so they\nare a snapshot of the job's address at the time the allocation was recorded\nand will not follow a later edit to the job."},"addressLine2":{"type":"string","description":"`addrs2`. See `addressLine1`."},"cityName":{"type":"string","description":"`ctynme`. See `addressLine1`."},"stateCode":{"type":"string","description":"`state_`: the trailing underscore is Sage's, presumably to clear a reserved\nword, and it is the column name, not a typo carried forward. See\n`addressLine1`."},"zipCode":{"type":"string","description":"`zipcde`. See `addressLine1`."},"status":{"$ref":"#/components/schemas/InventoryAllocationStatus","description":"`status`, and it decides what stock moves rather than merely describing the\nrow. `1` and `2` move nothing, `3` reduces available quantity, `4` reduces\navailable and both on-hand columns; inventory history is written for `3` and\n`4` only. See `InventoryAllocationStatus`."},"costOrder":{"type":"string","description":"`cstord`, Sage's \"Cost Order\".\n\n**OPDC writes the ticket number into it** (the same value as\n`transactionNumber`), so on any row this API created the two fields are equal\nand this one carries no independent information. Rows created in Sage may use\nthe column as Sage intends. Worth resolving against Sage's help before a\nreport reads it as a cost order."},"transactionDate":{"type":"string","format":"date","description":"`trndte`, the allocation date. Date, not an instant."},"approvalDate":{"type":"string","format":"date","description":"`aprdte`. Date, not an instant."},"deliveryDate":{"type":"string","format":"date","description":"`deldte`. Date, not an instant."},"invoiceDate":{"type":"string","format":"date","description":"`invdte`. Date, not an instant."},"deliveryMethod":{"type":"string","description":"`delvia`, free text. The write endpoint takes it as `deliveryVia`."},"description":{"type":"string","description":"`dscrpt`, free text.\n\n**In v2 this column decided whether a job cost was written**, by exact string\ncomparison against `\"To Requisition Stock to Job\"`: a free-text field\nsilently driving an accounting cascade. v3 moves the decision to `purpose`\n(`AllocationPurpose`), which the write result derives and returns. On a row\nwritten before that change, this field is still the only evidence of intent."},"userDefined1":{"type":"string","description":"`usrdf1`."},"userDefined2":{"type":"string","description":"`usrdf2`."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the ledger reference this allocation was stamped with, set to the\ndrawn sequence number at status `3` and `4`, and to `0` otherwise.\n\n**When it is non-zero it points at an `the underlying record` row that was never\ninserted.** The allocation consumes a number out of the ledger sequence and\nstamps it here, on `the underlying record` and on `the underlying record` without posting any journal. For\na job requisition that is more than cosmetic: a real cost lands against the\njob with no GL entry behind it, and the dangling reference makes job cost and\nthe ledger look reconciled when they are out of step by construction.\n\nNot typed `RecordNumber` (`@minValue(1)`) because `0` is a normal value here,\nmatching `PayableInvoice.ledgerRecord`."},"period":{"type":"integer","format":"int16","description":"The period the allocation posted into (`actper`).\n\nNote the column: `the underlying record`, which this allocation writes, spells the same\nconcept `actprd`. Sage uses `actper` on documents and `actprd` on the ledger\nand the rows a posting produces; both are published as `period`, so the split\nis invisible to a caller and matters only to someone writing SQL.\n`InventoryHistory.period` carries the full list."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. One of the columns `createdSince` filters on. Date, not an instant."},"userName":{"type":"string","description":"`usrnme`. Defaults to the SQL login when the caller sends none."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"year":{"type":"integer","format":"int16","description":"`postyr`."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InventoryAllocationLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, an inventory allocation (Sage 12-2), as read.\n\n**What the allocation actually moves is decided by `status`.** See\n`InventoryAllocationStatus`, which corrects the meanings v2 published for this\nfield. What it does **not** do is post a journal: the flow writes no `the underlying record`\nat all, while still consuming a ledger sequence number and stamping it on this\nrow. See `ledgerRecord`.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a query may narrow the row with `select`."},"InventoryAllocationInput":{"type":"object","required":["transactionDate","purpose","lines"],"properties":{"userName":{"type":"string"},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"Required when `purpose` is `JOB_REQUISITION`."},"phaseNumber":{"type":"integer","format":"int64","minimum":0,"default":0},"transactionDate":{"type":"string","format":"date"},"status":{"$ref":"#/components/schemas/InventoryAllocationStatus","description":"Absent means `1` (Open), which moves nothing. See `InventoryAllocationStatus`\nfor what each value moves.","default":1},"description":{"type":"string","description":"Free text, and **free text only** in v3.\n\nIn v2 this field decided whether a job cost was written, by exact string\ncomparison against `\"To Requisition Stock to Job\"`. It no longer affects\nposting at all: use `purpose`."},"purpose":{"$ref":"#/components/schemas/AllocationPurpose","description":"What the allocation is for. Replaces the magic string."},"deliveryVia":{"type":"string"},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"Optional posting period, as on ledger transactions: derived from\n`transactionDate` when absent, validated against `validPeriods` when present."},"year":{"anyOf":[{"type":"integer","format":"int16"},{"type":"null"}]},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InventoryAllocationLineInput"},"minItems":1}},"description":"An allocation to record."},"InventoryAllocationLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only per-line unique value on the table."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): **the allocation's, not the line's**. See\nthe model doc."},"idRef":{"type":"string","description":"The parent allocation's `id` (`_idref`), the column the child fetch joins on.\nRedundant when read nested under the parent, which is the only way it can be\nread, but the column is in the child `SELECT` and therefore on the wire."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`. The value that actually orders and distinguishes lines."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, a foreign key to ``recnum``.\n\n**The v3 write result for this same table already publishes it under this\nname** (`InventoryAllocationLineResult.partRecordNumber`), so the read model\nmatching it is what stops one resource spelling one column two ways."},"partDescription":{"type":"string","description":"`prtdsc`. Copied from the part master when the caller omits it."},"alphaNumber":{"type":"string","description":"`alpnum`, the alphanumeric part code a human reads.\n\nNote that the `Part` entity publishes this same column under the name\n`partCode`."},"unitDescription":{"type":"string","description":"`untdsc`, the unit of measure as text."},"inventoryQuantity":{"$ref":"#/components/schemas/Decimal","description":"`invqty`, the quantity moved. The write contract calls this `quantity`."},"inventoryCost":{"$ref":"#/components/schemas/Decimal","description":"`invcst`, the unit cost. The write contract calls this `cost`."},"inventoryTotal":{"$ref":"#/components/schemas/Decimal","description":"`invttl`, `inventoryQuantity * inventoryCost`. The write contract calls this\n`total`, and cross-checks it against the other two on the way in, returning\n422 naming both figures on a mismatch."},"serialNumber":{"type":"string","description":"`serial`. Note the column name: `InventoryHistory` and `InventorySerial`\nspell the same concept `sernum`. Nothing this API writes populates the\ncolumn, so a non-empty value here came from Sage."},"sourceLocation":{"type":"integer","format":"int32","description":"`invloc`, the location stock is drawn from, a foreign key to ``recnum``.\n\nThe column is named after the location *table*, not after its role, which is\nwhy the published name says which end it is."},"debitAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`dbtact`, the GL account the line debits."},"debitSubAccount":{"type":"integer","format":"int64","description":"`dbtsub`, the sub-account under `debitAccount`. Spelled to match\n`InventoryAllocationLineInput.debitSubAccount` on the write side of this same\nresource."},"destinationLocation":{"type":"integer","format":"int32","description":"`dstloc`. Present only on a transfer between locations, in which case the\nline writes two history rows and two `the underlying record` movements: one negative at\n`sourceLocation`, one positive here."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, one line of an inventory allocation, as read.\n\n**`recordNumber` is the parent's, repeated on every line.** It carries the\nallocation header's record number rather than a per-line sequence, so it is not\na line identifier and will not distinguish two lines of the same allocation;\n`id` and `lineNumber` will.\n\n**Three amounts on this model have a second set of names one hop away.**\n`inventoryQuantity`, `inventoryCost` and `inventoryTotal` are what the read\nsurface aliases `invqty` / `invcst` / `invttl` to; the write contract calls the\nsame three `quantity`, `cost` and `total`, in both\n`InventoryAllocationLineInput` and `InventoryAllocationLineResult`."},"InventoryAllocationLineInput":{"type":"object","required":["partNumber","quantity","cost","total","sourceLocation","debitAccount"],"properties":{"partNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The part's Sage record number.\n\nNote the name: on the read side the same value is published as\n`partRecordNumber`, but this write input keeps v2's `partNumber` spelling."},"partDescription":{"type":"string","description":"Free text. Looked up from the part master when omitted."},"unitDescription":{"type":"string"},"quantity":{"$ref":"#/components/schemas/Decimal","description":"Must be greater than zero."},"cost":{"$ref":"#/components/schemas/Decimal"},"total":{"$ref":"#/components/schemas/Decimal","description":"`quantity * cost`. **Cross-checked by the server**, which returns 422 naming\nboth figures on a mismatch. v2 accepted every amount on trust and would\nhappily write internally inconsistent accounting."},"sourceLocation":{"type":"integer","format":"int32","minimum":1},"destinationLocation":{"type":"integer","format":"int32","description":"Present only for a transfer between locations."},"debitAccount":{"$ref":"#/components/schemas/LedgerAccountNumber"},"debitSubAccount":{"type":"integer","format":"int64"}},"description":"One allocation line."},"InventoryAllocationLineResult":{"type":"object","required":["id","recordNumber","lineNumber","partRecordNumber","partDescription","quantity","cost","total","sourceLocation"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber"},"partDescription":{"type":"string"},"quantity":{"$ref":"#/components/schemas/Decimal"},"cost":{"$ref":"#/components/schemas/Decimal"},"total":{"$ref":"#/components/schemas/Decimal"},"sourceLocation":{"type":"integer","format":"int32"},"destinationLocation":{"type":"integer","format":"int32"}},"description":"An allocation line as written."},"InventoryAllocationResult":{"type":"object","required":["id","recordNumber","status","purpose","lines"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"status":{"$ref":"#/components/schemas/InventoryAllocationStatus"},"purpose":{"$ref":"#/components/schemas/AllocationPurpose"},"description":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InventoryAllocationLineResult"}}},"description":"An allocation as written."},"InventoryAllocationStatus":{"type":"integer","format":"int16","description":"``status``.\n\nStatus decides what moves. `1` and `2` record the allocation and its lines and\nmove no stock at all; `3` reduces available quantity; `4` reduces available,\n``qtyohn`` and the part-master total ``qtyohn``. An inventory-history\nrow is written for `3` and `4` only.\n\nEarlier v2 documentation described these values as draft, approved, delivered\nand invoiced. That was incorrect; the meanings below are the ones Sage uses.","x-enum-descriptions":["Open. Inventory is available. Moves nothing.","Review. Review before shipping. Moves nothing.","Hold Stock. Reserved for a job or work order. Reduces available quantity.","Shipped. Sent to a job. Reduces available and both on-hand columns."],"x-enum-varnames":["Open","Review","HoldStock","Shipped"],"enum":[1,2,3,4]},"InventoryAllocationWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun","period","year"],"properties":{"document":{"$ref":"#/components/schemas/InventoryAllocationResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into."}},"description":"The body of a successful `POST /inventory-allocations`.\n\n`jobCosts` is an array even though an allocation writes at most one `the underlying record`\nrow, so that every write endpoint reports its job costs the same way."},"InventoryBalance":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, a foreign key to ``recnum``."},"transQuantity":{"$ref":"#/components/schemas/Decimal","description":"`trnqty`, the quantity the originating transaction moved."},"quantityRemain":{"$ref":"#/components/schemas/Decimal","description":"`qtyrmn`, the part of `transQuantity` not yet drawn down. See the model doc."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. Date, not an instant."},"partPrice":{"$ref":"#/components/schemas/Decimal","description":"`prtprc`, the unit cost this row is valued at."},"sourceNumber":{"type":"integer","format":"int16","description":"`srcnum`, the GL transaction source, a foreign key to ``recnum``.\n\n**Deliberately not an enum**: `1`-`33` are installer-created, `34` and above\nare configured per site. Query the transaction-source lookup\n(`/query/transaction-source`)."},"ledgerRef":{"type":"integer","format":"int64","description":"`lgrref`.\n\n**A different column from the `lgrrec` that fourteen other models publish as\n`ledgerRecord`.** `lgrref` exists on only two tables in the schema, `the underlying record`\nand `the underlying record`, and the two are not the same value: `lgrrec` holds an `the underlying record`\nrecord number, while `lgrref` is a ledger *reference* sequence number. They\ncoincide on rows this API writes; they are not guaranteed to coincide on rows\nSage writes."}},"description":"`the underlying record`, a part's inventory balance rows.\n\n**Every row here was written by Sage.** This API neither creates nor maintains\n`the underlying record`; it only reads it.\n\nThe shape of the table (a transaction quantity, a remaining quantity and a\nunit price on one row) suggests a cost layer: a receipt quantity together with\nthe part of it not yet drawn down. That reading is inference from the columns,\nnot confirmed behaviour. **Confirm against Sage before a report treats\n`quantityRemain` as available stock**; `InventoryQuantity.quantityAvailable` is\nthe column that demonstrably means that, because the write path maintains it.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a query may narrow the row with `select`."},"InventoryConsumed":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, a foreign key to ``recnum``."},"transQuantity":{"$ref":"#/components/schemas/Decimal","description":"`trnqty`, the quantity consumed. Spelled as the column is; see\n`InventoryBalance.transQuantity`."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. Date, not an instant."},"transactionType":{"type":"integer","format":"int16","description":"`trntyp`.\n\n**Deliberately not an enum, and unlike `sourceNumber` it has no lookup table\nto point at.** No catalogue of this column's values has been confirmed, and\npublishing guessed meanings would be worse than publishing none. Resolve it\nagainst Sage's help before relying on particular values.\n\nNote it is a *transaction type*, unrelated to `sourceNumber` beside it, which\nis the GL source."},"sourceNumber":{"type":"integer","format":"int16","description":"`srcnum`, the GL transaction source, a foreign key to ``recnum``.\n\n**Deliberately not an enum**: `1`-`33` are installer-created, `34` and above\nare configured per site. Query the transaction-source lookup\n(`/query/transaction-source`)."},"ledgerRef":{"type":"integer","format":"int64","description":"`lgrref`, the ledger reference. **Not the `lgrrec` that `ledgerRecord` means\nelsewhere**. See `InventoryBalance.ledgerRef`, the only other model carrying\nthis column."}},"description":"`the underlying record`, inventory drawn out of stock.\n\nThe counterpart to `InventoryBalance`: the same part, quantity, date, source\nand ledger reference, without a remaining quantity and with a transaction type\ninstead of a price. See `InventoryBalance`'s doc for what that pairing suggests\nand for the same caveat: **this API neither writes nor maintains `the underlying record`**,\nand the column meanings are inferred from the schema rather than confirmed\nagainst Sage's documentation.\n\nNot to be confused with `InventoryHistory` (`the underlying record`), which is the movement\nlog OPDC does write, from the allocation and the purchase-order receipt.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a query may narrow the row with `select`."},"InventoryHistory":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this movement posted with.\n\n**On an allocation-sourced row this points at a ledger transaction that was\nnever inserted.** The allocation flow consumes a number out of the ledger\nsequence and stamps it on `the underlying record`, on this table and on `the underlying record` without\never writing the `the underlying record` row; it posts no GL at all. Every posted\nallocation therefore leaves a `lgrrec` that resolves to nothing and burns a\nsequence number.\n\nA receipt-sourced row (`sourceNumber` 13) is not affected: that flow does\nwrite its journal.\n\nNot typed `RecordNumber` (`@minValue(1)`): the column holds `0` where no\nreference was drawn."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, a foreign key to ``recnum``."},"locationNumber":{"type":"integer","format":"int32","description":"`locnum`, a foreign key to ``recnum``."},"sourceNumber":{"type":"integer","format":"int16","description":"`srcnum`, the GL transaction source, a foreign key to ``recnum``.\n\n**Deliberately not an enum.** `1`-`33` are inserted by the installer at\ncompany creation and are safe to name; `34` and above are added per site, so\nthe family is open. Query the transaction-source lookup\n(`/query/transaction-source`). The two values OPDC itself writes here are\n`23` (Inventory, 12-2 Inventory Allocation) and `13` (PO Receipt, 12-4).\n\nDeclared `int16` here, while `LedgerTransaction` publishes the same concept\nas `int32`."},"transactionRecord":{"type":"integer","format":"int64","description":"`trnrec`, the record number of the document that caused the movement.\n\nThe allocation writes `0` here, so on an allocation-sourced row this is not a\nusable back-pointer; the join back to the allocation goes through\n`ledgerRecord`, with the caveat above."},"transactionNumber":{"type":"string","description":"`trnnum`, the document number as a string. Written empty by the allocation."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. Date, not an instant."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. One of the columns `createdSince` filters on. Date, not an instant."},"serialNumber":{"type":"string","description":"`sernum`. Note the column: `InventoryAllocationLine` carries the same concept\nin a column spelled `serial`, and `InventorySerial` uses `sernum` as here."},"period":{"type":"integer","format":"int16","description":"The period the movement posted into (`actprd`).\n\n**Note the column is `actprd`, with ``actper`` one table away.** Sage\nspells the accounting period two ways, eight tables each, and the split is\nnot random: `actper` is on the documents (`the underlying record`, `the underlying record`, `the underlying record`,\n`the underlying record`, `the underlying record` and the payable, receivable and service payment tables)\nand `actprd` is on the ledger and the rows a posting produces (`the underlying record`,\n`the underlying record`, `the underlying record`, `the underlying record`, equipment cost and revenue, the period-status\ntable, and this one). The published name is `period` on all sixteen, so the\ndifference is invisible to a caller and only matters to someone writing SQL\nagainst the tables directly."},"partQuantity":{"$ref":"#/components/schemas/Decimal","description":"`prtqty`, the quantity moved."},"partPrice":{"$ref":"#/components/schemas/Decimal","description":"`prtprc`, the unit cost the movement was valued at."},"extendedPrice":{"$ref":"#/components/schemas/Decimal","description":"`extprc`, `partQuantity * partPrice` as the writing script computed it."},"year":{"type":"integer","format":"int16","description":"`postyr`."}},"description":"`the underlying record`, one movement of one part at one location.\n\n**The table is `the underlying record`, not `INVHIST`**: the seven-letter spelling names no\ntable in the schema.\n\nThis is a consequence table: rows are written by the inventory allocation and\nby the purchase-order receipt, never on their own. An allocation writes a row\nonly at status `3` or `4` (see `InventoryAllocationStatus`), so a part whose\nallocation sits at `1` or `2` has stock committed against it and no history row\nto show for it.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a query may narrow the row with `select`."},"InventoryLocation":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\n**This is the value the other inventory entities store as their location\ncode**, and there is a width disagreement worth knowing about before writing\na join: `recnum` is a 64-bit integer here, while every `locnum` / `invloc` /\n`dstloc` column that points at it is published as a 32-bit integer. A\nlocation number above the 32-bit range would be unrepresentable on the\nreferring side; no install is near that, but the asymmetry is real and is not\na modelling choice made here."},"locationName":{"type":"string","description":"`locnme`, the location's name. The only human-facing column on the table."},"employeeNumber":{"type":"integer","format":"int64","description":"`empnum`, a foreign key to ``recnum``: the employee responsible for the\nlocation. `0` when unset, which is why it is `int64` rather than\n`RecordNumber` (`@minValue(1)`), matching `Client.employeeNumber`."}},"description":"`the underlying record`, the inventory-location master.\n\nFour columns and that is the whole table. It is the lookup behind every\nlocation code the inventory entities carry: `locationNumber` on\n`InventoryQuantity`, `InventoryHistory` and `InventorySerial`, and\n`sourceLocation` / `destinationLocation` on `InventoryAllocationLine`, all\nresolve here. There is no second location table in the schema.\n\n**This API never writes `the underlying record`.** Locations are created in Sage; the entity\nis read-only.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a query may narrow the row with `select`."},"InventoryMovementResult":{"type":"object","required":["partNumber","location","quantityAvailableDelta","quantityOnHandDelta"],"properties":{"partNumber":{"$ref":"#/components/schemas/RecordNumber"},"location":{"type":"integer","format":"int32"},"quantityAvailableDelta":{"$ref":"#/components/schemas/Decimal"},"quantityOnHandDelta":{"$ref":"#/components/schemas/Decimal"},"historyRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"Present when an inventory-history row was written."}},"description":"One inventory movement as written."},"InventoryQuantity":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"idRef":{"type":"string","description":"`_idref`, the part row's `_idnum`.\n\nUnlike `idRef` on a line model, this one is **not** a child-collection\npointer: `the underlying record` is a top-level entity and there is no request that returns\nit nested under its part. The column is in the `SELECT` and therefore on the\nwire, and it is the only join back to the part that does not go through a\nrecord number."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, a foreign key to ``recnum``.\n\nThis is the part's record number, not the human-readable part code. That is\n`Part.partCode`."},"locationNumber":{"type":"integer","format":"int32","description":"`locnum`, a foreign key to ``recnum``."},"quantityOnHand":{"$ref":"#/components/schemas/Decimal","description":"`qtyohn`, the physically-present quantity.\n\n**On-hand and available move on different events.** A `3-Hold Stock`\nallocation reduces `quantityAvailable` only (the stock is reserved but still\nin the bin), while `4-Shipped` reduces both (see\n`InventoryAllocationStatus`). Reading only this column understates what is\nspoken for."},"quantityAvailable":{"$ref":"#/components/schemas/Decimal","description":"`qtyavl`, the unreserved quantity. Moves for both `3-Hold Stock` and `4-Shipped`."},"lastStockDate":{"type":"string","format":"date","description":"`lststk`. Date, not an instant."},"lastPickDate":{"type":"string","format":"date","description":"`lstpck`. Date, not an instant."},"lastCountDate":{"type":"string","format":"date","description":"`lstinv`, the last physical count. Note the column is named for the inventory, not the count."},"beginningQuantity":{"$ref":"#/components/schemas/Decimal","description":"`begqty`, the period-opening quantity. The write path seeds it to `0` on a new row."},"minRestockLevel":{"$ref":"#/components/schemas/Decimal","description":"`minstk`, the reorder point."},"maxRestockLevel":{"$ref":"#/components/schemas/Decimal","description":"`maxstk`, the restock-to level."}},"description":"`the underlying record`, one part's stock position at one location.\n\n**This entity has no `recordNumber`, and that is the table, not an omission.**\n`the underlying record` carries no `recnum` column at all, so the convention that `id` and\n`recordNumber` are the two guaranteed fields cannot hold here. `id` (`_idnum`)\nis the only required field on this model. The row's natural key is the pair\n`(prtnum, locnum)`.\n\nThe absence has a second, non-obvious consequence. Paging is made deterministic\nby ordering on `recnum` and then on `_idnum`; with no `recnum` this entity is\nordered on `_idnum` alone. Paging is still stable, but the order is UUID order,\nwhich is not part number order and not insertion order.\n\n**Only `id` is required.** Every other field is optional because a query may\nnarrow the row with `select`."},"InventorySerial":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only per-row unique value on this table."},"partNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The part's Sage record number, stored in the `recnum` **column**, not in\n`prtnum`. See the model doc: this is the one place where `recnum` is\npublished under another name, and it is deliberate."},"locationNumber":{"type":"integer","format":"int32","description":"`locnum`, a foreign key to ``recnum``."},"serialNumber":{"type":"string","description":"`sernum`, the serial number itself, the field that gives the row its\nmeaning. `InventoryAllocationLine` spells the same concept in a column named\n`serial`."},"stockedDate":{"type":"string","format":"date","description":"`stkdte`, when the unit was stocked. Date, not an instant."}},"description":"`the underlying record`, one serialised unit of a part at a location.\n\n**This model has no `recordNumber`.** `the underlying record` does have a `recnum` column, but\nthe value in it is the *part's* record number, repeated once per serial, so it\nis published as `partNumber` rather than normalised to `recordNumber`. The\npractical consequences: this entity has **no per-row Sage record number at\nall** (`id` is the only unique identifier), and rows are paged in `recnum`\norder with `_idnum` as the tiebreak.\n\nNote also that `partNumber` here aliases a `recnum` column, whereas the\n`partRecordNumber` on the other inventory entities aliases a `prtnum` column.\nThe two are the same kind of value but they are not read from the same place.\n\n**Only `id` is required.** Every other field is optional because a query may\nnarrow the row with `select`."},"Job":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): the job number a human reads in Sage."},"jobName":{"type":"string","description":"`jobnme`."},"shortName":{"type":"string","description":"`shtnme`. The abbreviated name Sage shows in pick lists."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clnnum`. The client this job bills to; ``recnum``, the `Client` entity's `recordNumber`."},"contact":{"type":"string","description":"`contct`. A person's name, free text. Not a foreign key."},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string"},"zipCode":{"type":"string"},"phoneNumber":{"type":"string","description":"`phnnum`. Free text as Sage stores it; no format is imposed."},"faxNumber":{"type":"string"},"architect":{"type":"integer","format":"int64","description":"`achtct`. A record number into the architect lookup. `0` when unset.\n\nInstall-specific target; not an enum and not resolvable from this response."},"lender":{"type":"integer","format":"int64","description":"`lender`. A record number into the lender lookup. `0` when unset."},"supervisor":{"type":"integer","format":"int64","description":"`sprvsr`. The supervising employee's record number. `0` when unset."},"salesperson":{"type":"integer","format":"int64","description":"`slsemp`. The selling employee's record number. `0` when unset.\n\nDistinct column from ``empnum``, which v3 spells `employeeNumber` on\n`Client`; a job carries dedicated salesperson, estimator and supervisor\ncolumns, so each keeps its role name here."},"estimator":{"type":"integer","format":"int64","description":"`estemp`. The estimating employee's record number. `0` when unset."},"userDefined1":{"type":"string","description":"`usrdf1`. Free text, meaning defined per install."},"userDefined2":{"type":"string","description":"`usrdf2`. Free text, meaning defined per install."},"payrollLocale":{"type":"integer","format":"int32","description":"`lcltax`. A record number into the payroll local-tax (locale) table. Install-specific; not an enum."},"salesTaxDistrict":{"type":"integer","format":"int32","description":"`slstax`.\n\n**The meaning of this column on `the underlying record` is uncertain.** On every other table\nthat exposes `slstax` it is a currency amount, published as `salesTax` or\n`salesTaxAmount`; here it is named and typed as a sales-tax district code.\nVerify against a live row before depending on either the name or the type."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lgract`. The job's income account.\n\n**This field was named `incomeAccount` in v2.** v3 publishes the column as\n`ledgerAccount`, the spelling used for it everywhere else in this API."},"department":{"type":"integer","format":"int64","description":"`dptmnt`. A record number into the department table. `0` when unset."},"discountTerms":{"type":"string","description":"`dscdte`. A **terms string** on `the underlying record`, not a date.\n\nThe same column carries a date on the invoice tables, where it is published\nas `discountDate`. Do not assume `YYYY-MM-DD` here."},"dueTerms":{"type":"string","description":"`duedte`. A terms string on `the underlying record`, not a date. See `discountTerms`."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"`dsccnt`. JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"financeRate":{"$ref":"#/components/schemas/Decimal","description":"`finchg`. Finance charge rate."},"retainageRate":{"$ref":"#/components/schemas/Decimal","description":"`retain`. Retainage rate held back from billings."},"contractAmount":{"$ref":"#/components/schemas/Decimal","description":"`cntrct`. The contract amount."},"beginningBalance":{"$ref":"#/components/schemas/Decimal","description":"`begbal`."},"endingBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`."},"bidDate":{"type":"string","format":"date","description":"`biddte`. Date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage date column."},"contractDate":{"type":"string","format":"date","description":"`ctcdte`. Date, not an instant."},"startDate":{"type":"string","format":"date","description":"`sttdte`. Date, not an instant."},"completionDate":{"type":"string","format":"date","description":"`cmpdte`. Date, not an instant."},"jobType":{"type":"integer","format":"int16","description":"`jobtyp`, a foreign key to ``recnum``. Install-specific, so the codes\nare not enumerated in this contract; query the job-type lookup rather than\nhardcoding values."},"status":{"$ref":"#/components/schemas/JobStatus","description":"`status`. Job status does not restrict posting. See `JobStatus`."},"postToWip":{"type":"integer","format":"int16","description":"`pstwip`. A `0`/`1` flag, not a boolean on the wire: whether the job posts to work in progress."},"certifiedPayroll":{"type":"integer","format":"int16","description":"`crtfid`. A `0`/`1` flag: whether the job requires certified payroll reporting."},"contractNumber":{"type":"string","description":"`connum`. **Free text, not a record number**, despite the `Number` suffix:\nthe contract identifier as the customer writes it."},"clientPO":{"type":"string","description":"`pchord`. **Free text**: the client's own purchase-order number for this job. Not a `the underlying record` foreign key."},"noteText":{"type":"string","description":"`ntetxt`."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`. A `0`/`1` flag despite the `is` prefix; Sage stores an integer and v3 does not reshape it."}},"description":"`the underlying record`, the job (3-5 Jobs).\n\n**`the underlying record` is the job header, not the job cost.** Job costs live in `the underlying record`,\nwhich is a separate entity.\n\nThere is no child collection on this entity, and `the underlying record` has no `lstupd`\ncolumn, so `lastUpdated` filtering is not supported here.\n\n**Only `id` and `recordNumber` are guaranteed.** A query may narrow the row\nwith `select`, so every other field is absent unless it was asked for."},"JobCost":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). Sage's own \"Record#\" on this table, unlike `JobPhase`, `JobPaygroup` and `PreLien`, whose `recnum` is the job number."},"workOrder":{"type":"string","description":"`wrkord`, a foreign key to ``ordnum``. A character column, not a number."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``: the `Job` entity's `recordNumber`."},"transactionNumber":{"type":"string","description":"`trnnum`. Caller-supplied on the originating document; not generated and not unique."},"description":{"type":"string","description":"`dscrpt`."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. Date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage date column."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. Date, not an instant."},"period":{"type":"integer","format":"int16","description":"`actprd`, the accounting period the cost posted into.\n\nThe column is `actprd` here and on `the underlying record`, `the underlying record`, `the underlying record`, `the underlying record` and\n`the underlying record`, but **`actper`** on `the underlying record`, `the underlying record` and `the underlying record`. One concept,\ntwo column spellings, one published name."},"sourceNumber":{"type":"integer","format":"int16","description":"`srcnum`, a foreign key to ``recnum``.\n\nVerified system-defined for 1-33; **34 and above are install-specific**, so\nthis is deliberately not an enum; query the transaction-source lookup. A job\ncost created as a consequence of a journal inherits the originating source\nrather than `31` (6-3 Job Costs), which is for costs entered by hand."},"status":{"$ref":"#/components/schemas/JobCostStatus","description":"`status`. See `JobCostStatus`, **not** `JobStatus`, which is `the underlying record`'s."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, a foreign key to ``phsnum``. Note the target is the phase number, not ``recnum``, which is the job number."},"billingStatus":{"$ref":"#/components/schemas/JobCostBillingStatus","description":"`bllsts`. See `JobCostBillingStatus`."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific. Not an enum; query the cost-code lookup (`CostCode`). A\nstring decimal because the column has three decimal places, which is also\nwhy `CostCode.recordNumber` being an integer is a live defect; see that model."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract, `5` Other are Sage\npresets; **`6` to `9` are defined per company** and were all four configured\nat this customer. Query the cost-type lookup (`CostType`).\n\nThis API defaults the field to `1` in three write paths and validates it\nnowhere, so a cost of unknown type is silently classified as Material."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``: the `Vendor` entity."},"equipmentNumber":{"type":"integer","format":"int64","description":"`eqpnum`, a foreign key to ``recnum``. `0` when unset."},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`empnum`, a foreign key to ``recnum``. `0` when unset."},"payrollRecord":{"type":"integer","format":"int64","description":"`the underlying record`, a foreign key to ``recnum``. Set when the cost came from a posted paycheck."},"payType":{"type":"integer","format":"int16","description":"`paytyp`. The payroll pay type behind a labour cost. Install-configurable; not an enum."},"costHours":{"$ref":"#/components/schemas/Decimal","description":"`csthrs`, Sage's \"Hours\"."},"costAmount":{"$ref":"#/components/schemas/Decimal","description":"`cstamt`, Sage's \"Cost Amount\". The figure the GL engine posts."},"billingQuantity":{"$ref":"#/components/schemas/Decimal","description":"`blgqty`."},"billingAmount":{"$ref":"#/components/schemas/Decimal","description":"`blgamt`."},"pieces":{"$ref":"#/components/schemas/Decimal","description":"`pieces`. A piece count for piece-rate payroll, not an amount."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, Sage's \"Link#\": the ``recnum`` this cost was posted alongside.\n\n**This is the only join back to the journal**, and it is a bare number with\nno declared foreign key in Sage's own dictionary. A job cost with no journal\nbehind it is representable."},"grossWage":{"$ref":"#/components/schemas/Decimal","description":"`grswge`."},"overrideBilling":{"type":"integer","format":"int16","description":"`ovrrde`, Sage's \"Override Billing Amount\". A `0`/`1` flag stored as a smallint, not a boolean on the wire."},"billingTotal":{"$ref":"#/components/schemas/Decimal","description":"`blgttl`."},"billingActive":{"type":"integer","format":"int16","description":"`active`, Sage's \"Billing in process\". A `0`/`1` flag; the published name says `billingActive` and the column says `active`."},"invoiceRecord":{"type":"integer","format":"int64","description":"`acrinv`, Sage's \"Invoice Record#\": the ``recnum`` this cost was billed on. `0` when unbilled."},"shownMarkup":{"$ref":"#/components/schemas/Decimal","description":"`shwamt`, Sage's \"Shown Markup\"."},"overheadAmount":{"$ref":"#/components/schemas/Decimal","description":"`ovhamt`."},"profitAmount":{"$ref":"#/components/schemas/Decimal","description":"`pftamt`."},"taxable":{"type":"integer","format":"int16","description":"`taxabl`, Sage's \"Subject to sales tax\". A `0`/`1` flag."},"userName":{"type":"string","description":"`usrnme`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"year":{"type":"integer","format":"int16","description":"`postyr`, the posting year. Published as `year` here and as `postingYear` on v2's read models."}},"description":"`the underlying record`, a job cost (6-3 Job Costs).\n\n**`the underlying record` is job cost; `the underlying record` is the job.** The two are easy to confuse and\nthe names do not help; reaching for \"job cost\" and landing on `the underlying record` is the\nsingle most re-made mistake against this schema.\n\nThe two status families do not even share a shape: `JobCostStatus` is\nOpen/Review/Dispute and `JobStatus` is Bid/Refused/Contract/Current/Complete/\nClosed, so a caller applying one to the other reads a live contract as a\ndisputed one.\n\nThere is no child collection on this entity, and no `lstupd` column, so\n`lastUpdated` filtering is not supported here.\n\n**Only `id` and `recordNumber` are required**; a query may narrow the row with\n`select`.\n\nSage carries four equipment columns this API does not return: `blgunt`\n(Equipment Unit), `eqptyp`, `eqpunt` and `eqpqty`. Worth knowing before\nconcluding the API cannot answer an equipment-allocation question."},"JobCostBillingStatus":{"type":"integer","format":"int16","description":"Job-cost billing status.","x-enum-varnames":["Open","Billed","Paid"],"enum":[1,2,3]},"JobCostInput":{"type":"object","required":["jobNumber","costCode","costType","costAmount"],"properties":{"jobNumber":{"$ref":"#/components/schemas/RecordNumber"},"phaseNumber":{"type":"integer","format":"int64","minimum":0,"description":"Sage phase. `0` when the job is not phased.","default":0},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Query the cost-code lookup rather than hardcoding."},"costType":{"type":"integer","format":"int16","minimum":1,"description":"Install-specific above 5. Query the cost-type lookup rather than hardcoding."},"status":{"$ref":"#/components/schemas/JobCostStatus","description":"Job cost status. Defaults to `1`, Open.","default":1},"billingStatus":{"$ref":"#/components/schemas/JobCostBillingStatus","description":"Job cost billing status. Defaults to `1`, Open.","default":1},"description":{"type":"string","maxLength":30},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber"},"costHours":{"$ref":"#/components/schemas/Decimal"},"costAmount":{"$ref":"#/components/schemas/Decimal"},"billingAmount":{"$ref":"#/components/schemas/Decimal"}},"description":"A job cost produced by a document cascade.\n\n`period` and `year` are absent on input: the server resolves them from the\ndocument date and reports them back on the write result. In v2 they were\nhonoured verbatim on job costs while being ignored on the header, so a client\nsending `\"year\": null` was correct only by accident."},"JobCostResult":{"type":"object","required":["id","recordNumber","jobNumber","phaseNumber","costCode","costType","costAmount","billingAmount"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"jobNumber":{"$ref":"#/components/schemas/RecordNumber"},"phaseNumber":{"type":"integer","format":"int64"},"costCode":{"$ref":"#/components/schemas/Decimal"},"costType":{"type":"integer","format":"int16"},"costAmount":{"$ref":"#/components/schemas/Decimal"},"billingAmount":{"$ref":"#/components/schemas/Decimal"}},"description":"A job cost as written."},"JobCostStatus":{"type":"integer","format":"int16","description":"``status`` on a job cost written as part of a document cascade.\n\nThis is the status of the job **cost** row, not of the job. A job's own status\nis ``status``, a different and larger family. See `JobStatus`.","x-enum-varnames":["Open","Review","Dispute"],"enum":[1,2,3]},"JobPaygroup":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only unique key on this table."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the job number**, a foreign key to ``recnum``, not a\nper-row record number. Not unique. See the model note."},"idRef":{"type":"string","description":"`_idref`, the parent job's `id`.\n\nOn `SubcontractLine` this column is the join key of a child fetch. Here it is\non the wire with nothing that consumes it: a `JobPaygroup` cannot be fetched\nas a child of its `Job`, so query this entity directly and match on\n`recordNumber`."},"position":{"type":"integer","format":"int32","description":"`reggrp`, a foreign key to ``recnum`` (Payroll Positions).\n\n**The published name says `position` and the column says `reggrp`**, which\nreads like \"regular group\". Sage's own label for the column is \"Position\".\nInstall-specific; not an enum."},"paygroup":{"type":"integer","format":"int32","description":"`paygrp`, a foreign key to ``recnum`` (5-3-4 Paygroups). Install-specific; query the paygroup lookup."},"pieceRate":{"$ref":"#/components/schemas/Decimal","description":"`pcerte`, Sage `Numeric(6,3)`. A per-piece rate, not an amount."}},"description":"`the underlying record`, a job paygroup override (3-5 Jobs, the Paygroups tab).\n\nOne row assigns a paygroup and a piece rate to a payroll position on one job,\nwhich is how a job that pays differently from the company default is expressed.\n\n**`recnum` on this table is the JOB number, not this row's identity.** Sage\nlabels it \"Job#\" and declares it a foreign key to ``recnum``.\n`recordNumber` is therefore not unique; `id` is. Same shape as `JobPhase` and\n`PreLien`."},"JobPhase":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only unique key on this table; see the model note."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the job number**, a foreign key to ``recnum``, not a\nper-row record number. Not unique. See the model note."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, Sage's \"Phase#\". The identifier every `phsnum` foreign key targets."},"phaseName":{"type":"string","description":"`phsnme`, which Sage labels \"Description\". Published as `phaseName`."},"billingAmount":{"$ref":"#/components/schemas/Decimal","description":"`bllamt`, Sage `Numeric(9,2)`."},"retention":{"$ref":"#/components/schemas/Decimal","description":"`retain`, Sage `Numeric(2,2)`. A retention **rate**, not an amount.\n\nThe same column is published as `retainageRate` on `the underlying record` and\n`primaryRate` on `the underlying record`: three names for one column."},"unitDescription":{"type":"string","description":"`untdsc`, Sage's \"Unit\".\n\nSpelled `unitDescription` here and `unitDesc` on `the underlying record`: one column, two\npublished names, both carried forward."},"unitQuantity":{"$ref":"#/components/schemas/Decimal","description":"`untqty`, Sage `Numeric(9,2)`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a job phase (3-5 Jobs, the Phases tab).\n\n**`recnum` on this table is the JOB number, not this row's identity.** Sage\nlabels it \"Job#\" and declares it a foreign key to ``recnum``. So\n`recordNumber` here is not unique (every phase of one job carries the same\nvalue), and `id` (`_idnum`) is the only key that identifies a row.\n`phaseNumber` is what distinguishes phases within a job, and it is what every\n`phsnum` foreign key in the product points at (`Subcontract.phaseNumber`,\n`JobCost.phaseNumber`), **not** `recordNumber`.\n\n`the underlying record` and `the underlying record` share this shape; `the underlying record` does not: its `recnum` really\nis a record number.\n\nA phase is reachable only by querying this entity: a `Job` does not carry its\nphases as a child collection.\n\nSage carries `mdldsc` (\"Model\", `Character(5)`), which this API does not\nreturn."},"JobStatus":{"type":"integer","format":"int16","description":"``status``, the job's own status.\n\nSix members, and note it does **not** share the `1-Open` / `2-Review` /\n`3-Dispute` opening that several other Sage status families have. A caller that\nlearned `3` means Dispute from a job cost and applied that reading to a job\nwould read a live contract as a disputed one. Status codes are per record type;\nnever carry a meaning across from one to another.","x-enum-varnames":["Bid","Refused","Contract","Current","Complete","Closed"],"enum":[1,2,3,4,5,6]},"JobType":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Job Type#\". The value `Job.jobType` carries."},"typeName":{"type":"string","description":"`typnme`.\n\n**The same column is published as `costTypeName` on `the underlying record`**: two lookup\ntables, one column name, two published names."}},"description":"`the underlying record`, the job-type lookup (3-5 Jobs).\n\n**This is the table `Job.jobType` points at.** Job types are defined per\ninstallation, so they are never enumerated in this contract; this endpoint is\nthe only way to resolve a code to a name.\n\nThree columns, and `recnum` is Sage `Numeric(3)`, so job type codes run 1-999.\n\nSage carries `ntetxt` on this table; this API does not return it."},"LedgerAccount":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Account#\": the general ledger account number itself.\n\nTyped `RecordNumber` rather than `LedgerAccountNumber` because it is this\ntable's own record number; the `LedgerAccountNumber` scalar marks the\n*foreign keys* that point back here."},"shortName":{"type":"string","description":"`shtnme`, Sage `Character(30)`. The abbreviated name Sage shows in pick lists."},"longName":{"type":"string","description":"`lngnme`, Sage's \"Account Name\"."},"subsidiaryType":{"type":"integer","format":"int16","description":"`subact`, Sage's \"Subsidiary Type\", `Numeric(1)`.\n\n**Not a subaccount number**, despite the column name. `subact` is published\nunder four different meanings across the product (`subAccount`, `subaccount`,\n`subaccountNumber` and this one), and on `the underlying record` alone it is a type code\nsaying whether the account is subsidiary-tracked. Install-specific; not an\nenum."},"summaryAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`sumact`, a self-referencing foreign key to ``recnum``: the account this one rolls up into. Null when it does not roll up."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1`-`5` are Sage presets and **`6`-`9` are per-company**, so this is\ndeliberately not an enum. Query the cost-type lookup (`CostType`)."},"beginningBalance":{"$ref":"#/components/schemas/Decimal","description":"`begbal`, Sage `Numeric(12,2)`."},"endBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`, Sage's \"Ending Balance\". Published as `endingBalance` on `the underlying record`; see the model note."},"lastCheck":{"type":"integer","format":"int64","description":"`nxtchk`, Sage's \"LastCheck\".\n\nThe column abbreviation reads \"next check\" and Sage's own label says *Last*.\nPublished under Sage's label. Meaningful only on a cash account."},"lastDeposit":{"type":"integer","format":"int64","description":"`nxtdep`, Sage's \"LastDeposit\". Same column/label mismatch as `lastCheck`."},"startingBalance":{"$ref":"#/components/schemas/Decimal","description":"`strbal`, Sage `Numeric(12,2)`. Distinct from `beginningBalance`: the balance the account was opened at."},"accountType":{"type":"integer","format":"int16","description":"`acttyp`, Sage `Numeric(2)`.\n\nThe account's category. **Deliberately not an enum**: the category an account\nnumber actually falls into is decided by the seventeen account-number ranges\non `LedgerSetup`, which are configured per installation. Resolve against those\nrather than assuming a fixed set here."},"debitCredit":{"type":"integer","format":"int16","description":"`dbtcrd`, Sage `Numeric(1)`. Which side the account normally carries: `1` is\ndebit-normal, `2` is credit-normal.\n\n**This is what signs a balance movement.** A debit-normal account's balance\nmoves by debit − credit; a credit-normal account's moves by the reverse. A\ncaller reconciling `the underlying record` deltas against `the underlying record` lines must apply it."},"notes":{"type":"string","description":"`ntetxt`, a Sage memo field. **Published as `notes`, not `noteText`**. See the model note."},"jobAsSubaccount":{"type":"integer","format":"int16","description":"`jobsub`, Sage's \"Job as Subaccount\". A `0`/`1` flag: whether job numbers act as this account's subaccounts."},"isCreditCard":{"type":"integer","format":"int16","description":"`iscrcd`, Sage's \"Is Credit Card Account\". A `0`/`1` flag stored as a smallint, not a boolean on the wire, despite the `is` prefix."},"inactive":{"type":"integer","format":"int16","description":"`inactv`, a `0`/`1` flag. **Published as `inactive`, not `isInactive`**. See the model note."},"bankingCloudId":{"type":"string","description":"`bnk_id`, Sage `Character(36)`. The Banking Cloud identifier for a linked bank feed."},"lastBankingTrnId":{"type":"integer","format":"int64","description":"`trn_id`, Sage's \"Last Banking Cloud Tran I[d]\". The last transaction pulled from that feed."}},"description":"`the underlying record`, the chart of accounts (1-7 General Ledger Accounts).\n\n`recnum` here **is** the account number: it is what every `lgract` foreign key\nin the product targets: `ledgerAccount` on `Job`, `LedgerTransactionLine`,\n`PayableInvoiceLine` and `LedgerBalance`, `controlAccount` on\n`LedgerSubaccount` and `LedgerSubBalance`, and all thirty-odd account columns\non `LedgerSetup`.\n\n**Three field names on this model diverge from the rest of the API, and are\npublished unchanged.** `ntetxt` is `notes` here where 43 other models say\n`noteText`; `inactv` is `inactive` here where every other model says\n`isInactive`; and `endbal` is `endBalance` here where `the underlying record` publishes the\nsame column as `endingBalance`.\n\n**There is no `balances` collection on this entity.** An account does not carry\nits per-period balances; those are `the underlying record` rows and are read separately.\n\n**Only `id` and `recordNumber` are required**; a query may narrow the row with\n`select`."},"LedgerAccountNumber":{"type":"integer","format":"int64","minimum":1,"description":"A ledger account number."},"LedgerBalance":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Record#\". A real per-row record number on this table."},"idRef":{"type":"string","description":"`_idref`, the owning `LedgerAccount`'s `id`.\n\nOn `SubcontractLine` this column is a child fetch's join key. Here it is on\nthe wire with nothing that consumes it: a balance cannot be fetched as a\nchild of its account. Filter on `ledgerAccount` instead."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lgract`, a foreign key to ``recnum``. The redundant-but-usable join key, and the one that actually works."},"year":{"type":"integer","format":"int16","description":"`postyr`, Sage's \"Year\". The posting year, spelled `postyr` on every table that has it."},"period":{"type":"integer","format":"int16","description":"`actprd`, the accounting period, 1-12.\n\n`actprd` here and on `the underlying record`, `the underlying record`, `the underlying record`, `the underlying record` and `the underlying record`, but\n**`actper`** on `the underlying record`, `the underlying record` and `the underlying record`."},"balance":{"$ref":"#/components/schemas/Decimal","description":"`balnce`, Sage `Numeric(12,2)`. The account's balance at the end of this period."},"budget":{"$ref":"#/components/schemas/Decimal","description":"`budget`, Sage `Numeric(12,2)`. The budgeted figure for the same account and period."}},"description":"`the underlying record`, one account's balance for one accounting period (1-7).\n\nOne row per account per period per year. **These are the rows a posting\nmoves**: writing a ledger transaction propagates its delta into `the underlying record` (and\n`the underlying record`) as a consequence, and for a balance-sheet account the delta carries\nforward from the posting period through period 12. Rows missing for earlier\nperiods are created as part of that propagation.\n\n**There is no `subBalances` collection on this entity.** Subaccount balances\nare `the underlying record` rows and are read separately, as `LedgerSubBalance`."},"LedgerSetup":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The whole identity of this entity; there is no `recnum`."},"accountRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowlgr`. Lowest account number the chart of accounts may use.\n\n**The seventeen `*RangeMin`/`*RangeMax` pairs below are how Sage assigns a\ncategory to an account**: the category is decided by which range the account\nnumber falls in, and each range includes both endpoints. Ten pairs from cash\nthrough owners' drawing are the balance sheet; seven from operating income\nthrough after-tax expense are the income statement. They are per-install,\nwhich is why `LedgerAccount.accountType` is not an enum."},"accountRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghlgr`. Highest account number the chart of accounts may use."},"cashAccountRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowcsh`. Lowest account number treated as a cash account."},"cashAccountRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghcsh`."},"currentAssetsRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowast`. Lowest account number treated as a current asset."},"currentAssetsRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghast`."},"workInProgressRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowwip`. Lowest account number treated as work in progress."},"workInProgressRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghwip`."},"otherAssetsRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowoth`. Lowest account number treated as an other asset."},"otherAssetsRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghoth`."},"fixedAssetsRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowfix`. Lowest account number treated as a fixed asset."},"fixedAssetsRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghfix`."},"accumulatedDepreciationMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowdpr`. Lowest account number treated as accumulated depreciation. Note the pair is `...Min`/`...Max` without `Range`, unlike its sixteen siblings."},"accumulatedDepreciationMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghdpr`."},"currentLiabilitiesRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowcur`. Lowest account number treated as a current liability."},"currentLiabilitiesRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghcur`."},"longTermLiabilitiesRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowlng`. Lowest account number treated as a long-term liability."},"longTermLiabilitiesRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghlng`."},"equityRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`loweqt`. Lowest account number treated as equity."},"equityRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hgheqt`."},"ownersDrawingRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowown`. Lowest account number treated as an owners' drawing account."},"ownersDrawingRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghown`."},"operatingIncomeRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowopr`. Lowest account number treated as operating income."},"operatingIncomeRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghopr`."},"otherIncomeRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowinc`. Lowest account number treated as other income."},"otherIncomeRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghinc`."},"directExpenseRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowdir`. Lowest account number treated as a direct job expense.\n\n**This range decides whether a job cost is created.** A purchase-order receipt\nline posting into it produces a `the underlying record` row; one posting outside it does not."},"directExpenseRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghdir`."},"equipmentExpenseRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`loweqp`. Lowest account number treated as an equipment expense."},"equipmentExpenseRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hgheqp`."},"overheadExpenseRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowovh`. Lowest account number treated as an overhead expense."},"overheadExpenseRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghovh`."},"adminExpenseRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowadm`. Lowest account number treated as an administrative expense."},"adminExpenseRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghadm`."},"afterTaxExpenseRangeMin":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lowtax`. Lowest account number treated as an after-tax expense."},"afterTaxExpenseRangeMax":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`hghtax`."},"dedicatedAccountsReceivableAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ded_ar`. GL control account for accounts receivable."},"dedicatedServiceReceivablesAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ded_sr`. GL control account for service receivables when the company keeps them separate from AR. Null when it does not."},"dedicatedAccountsPayableAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ded_ap`. GL control account for accounts payable.\n\n**The account credited when an AP invoice or a purchase-order receipt posts.**"},"dedicatedInventoryAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`dedinv`. GL control account for inventory.\n\n**A purchase-order receipt line updates inventory quantities only when the\nline debits this account.**"},"dedicatedEquipmentAssetAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`dedeqp`. GL control account for equipment held as a fixed asset."},"dedicatedDepreciationAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`deddpr`. GL control account for depreciation."},"dedicatedLoanAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`dedlns`. GL control account for loans."},"salesTaxPayableAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ar_tax`. GL account credited for sales tax payable on receivables."},"financeChargesAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ar_fin`. GL account for finance charges billed to clients."},"discountsGivenAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ar_dsc`. GL account for discounts given to clients."},"workInProgressPayrollAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ar_wip`. GL account for payroll charged to work in progress."},"retainedEarningsAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ar_ern`. GL account holding retained earnings."},"equipmentJobCostAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`eqpjcs`. GL account for equipment costs charged to jobs."},"equipmentRepairAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`eqprpr`. GL account for equipment repair costs."},"equipmentRevenueAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`eqprvn`. GL account for equipment revenue."},"workersCompAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ap_cmp`. GL account for workers' compensation."},"discountsEarnedAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ap_dsc`. GL account for discounts earned on vendor invoices."},"purchaseOrderExpenseAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ap_tax`. GL account for sales tax charged on purchases.\n\n**The name and the column disagree.** `ap_tax` is the AP sales-tax account;\nthe published name says purchase-order expense. Published unrenamed, because\nrenaming it would break the contract."},"purchaseOrderSalesTaxSubacct":{"type":"integer","format":"int64","description":"`ap_sub`. Subaccount (``recnum``) for the sales tax charged on purchases."},"freightWipCostAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`fv_wip`. GL account for freight and price variance charged to work in progress."},"freightDirectCostAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`fv_dcs`. GL account for freight and price variance charged to direct job cost."},"freightDirectCostSubacct":{"type":"integer","format":"int64","description":"`fv_dsb`. Subaccount (``recnum``) for the freight/variance direct-cost posting."},"freightOverheadCostAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`fv_ohc`. GL account for freight and price variance charged to overhead."},"freightOverheadCostSubacct":{"type":"integer","format":"int64","description":"`fv_ohs`. Subaccount (``recnum``) for the freight/variance overhead posting."},"inventoryValuationMethod":{"type":"integer","format":"int64","description":"`invmth`. The inventory valuation method the company is configured for.\n\n**A code, and the meanings are Sage's, not this API's.** Deliberately not an\nenum: resolve the value against Sage's own documentation."},"inventoryOffsetAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`invofs`. GL offset account for inventory postings."},"defaultAccountPayroll":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`dftact`. Default GL account configured for payroll distribution.\n\nPayroll posted through this API names its own accounts and never falls back\nto it."},"holdbackDueAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ar_hld`. GL account for holdback (retainage) receivable."},"holdbackPayableAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ap_hld`. GL account for holdback (retainage) payable."},"fiscalYearEndDate":{"type":"string","format":"date","description":"`fscyrd`. Last day of the current fiscal year. Date, not an instant.\n\n**Every accounting period window is derived from it**: period 1 begins the\nday after the previous fiscal year end, and each later period begins one\nmonth after the one before. There are always 12 periods; Sage's\nperiod-count columns do not exist in this schema."},"currentPeriod":{"type":"integer","format":"int16","description":"`curprd`. The accounting period currently open for posting, 1-12.\n\nv2 also returns `currentPeriodMin`, `currentPeriodMax`, `year` and\n`validPeriods`, all derived from this field and `fiscalYearEndDate`. **v3 does\nnot**. See the model note."},"verifyDatePeriod":{"type":"integer","format":"int16","description":"`vfydte`. Whether Sage verifies a transaction's date against the period it posts to. Reported as stored; this API does not act on it."},"lastYearLedgerRef":{"type":"integer","format":"int64","description":"`lstlgr`. The ledger reference Sage records against the previous fiscal year.\n\nReported as stored. **References for new transactions do not come from it.**"},"verifyCostType":{"type":"integer","format":"int16","description":"`blkexp`. A cost-type verification setting from Sage's ledger setup. Reported as stored; this API does not act on it."},"useTaxAccountsPayableAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`use_ap`. GL account credited for use tax payable."},"useTaxWorkInProgressAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`usewip`. GL account use tax is charged to when the purchase is work in progress."},"useTaxDirectExpenseAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`usedcs`. GL account use tax is charged to when the purchase is a direct job expense."},"useTaxOverheadAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`useohc`. GL account use tax is charged to when the purchase is overhead."},"useTaxEquipmentExpenseAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`useeqc`. GL account use tax is charged to when the purchase is an equipment expense."},"requireUseTaxAccount":{"type":"integer","format":"int16","description":"`requse`. Whether Sage requires a use tax account to be assigned on a purchase."},"periodDate":{"type":"integer","format":"int16","description":"`prddte`. The posting-period policy: `0` posts into the period currently\nopen, `1` derives the period from the transaction date.\n\nEither way the period is resolved for the caller: a write does not have to\nstate one, and a date resolving to a period with no ledger balances is\nrejected rather than posted."}},"description":"`the underlying record`, the company's ledger setup (1-8 General Ledger Setup).\n\n**One row per company**, so a query of this entity returns exactly one record.\nIt is the configuration every other GL decision reads: which account numbers\nfall in which category, which accounts AP/AR/inventory/payroll post to, and\nwhich accounting period is open.\n\n**`the underlying record` has NO `recnum` column**, so this entity has no `recordNumber` at\nall. It is one of only two in that position (`PeriodStatus` is the other), and\nthe rule that `id` and `recordNumber` are always present does not hold here.\nWith a single row and no record number, `id` is the whole identity.\n\n**Four fields v2 returned on this entity are absent from v3:**\n`currentPeriodMin`, `currentPeriodMax`, `year` and `validPeriods`. All four are\nderived rather than stored (computed from `fiscalYearEndDate` and\n`currentPeriod`), and v3 returns only stored columns. A caller migrating from\nv2 must compute them, or keep using v2 until they are restored.\n\nTwo names on this model do not match the Sage column behind them, and are\npublished unchanged: **`purchaseOrderExpenseAccount` is `ap_tax`**, the AP\nsales-tax account, and `purchaseOrderSalesTaxSubacct` is its subaccount.\n\n`verifyDatePeriod`, `lastYearLedgerRef` and `verifyCostType` are reported as\nSage stores them; this API does not act on any of them.\n\nSage's audit columns (`insdte`, `insusr`, `upddte`, `updusr`) are not returned.\n\n**Only `id` is required.** Every other field is optional, both because a query\nmay narrow the row with `select` and because most of these columns are nullable\nin the customer schema."},"LedgerSubBalance":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Record#\". A real per-row record number on this table."},"idRef":{"type":"string","description":"`_idref`, the owning row's `id`. On the wire, but nothing joins on it; this entity is not fetched as a child."},"controlAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ctract`, Sage's \"Ledger Controlling Account\", a foreign key to ``recnum``."},"subAccount":{"type":"integer","format":"int64","description":"`subact`, Sage's \"Ledger Subaccount\", a foreign key to ``recnum``.\n\n**Here `subact` really is a subaccount number.** The same column on `the underlying record`\nis a subsidiary *type* code, published there as `subsidiaryType`, so the\ncolumn name alone does not tell you which you have; the table does."},"year":{"type":"integer","format":"int16","description":"`postyr`, the posting year."},"period":{"type":"integer","format":"int16","description":"`actprd`, the accounting period, 1-12. `actper` on `the underlying record`/`the underlying record`/`the underlying record`; see `LedgerBalance.period`."},"balance":{"$ref":"#/components/schemas/Decimal","description":"`balnce`, Sage `Numeric(12,2)`."},"budget":{"$ref":"#/components/schemas/Decimal","description":"`budget`, Sage `Numeric(12,2)`."}},"description":"`the underlying record`, one subaccount's balance for one accounting period (1-7).\n\nOne row per controlling account per subaccount per period per year. This is the\nsubaccount analogue of `LedgerBalance`, and the third table a posting\npropagates its delta into."},"LedgerSubaccount":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Subaccount#\". The value `LedgerSubBalance.subAccount` and ``subact`` carry."},"shortName":{"type":"string","description":"`shtnme`, Sage `Character(30)`."},"longName":{"type":"string","description":"`lngnme`, Sage's \"Subaccount Name\"."},"controlAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`ctract`, a foreign key to ``recnum``: the account this subaccount partitions."},"beginningBalance":{"$ref":"#/components/schemas/Decimal","description":"`begbal`, Sage `Numeric(12,2)`."},"endBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`, Sage's \"Ending Balance\". `endingBalance` on `the underlying record`; see the model note."},"startingBalance":{"$ref":"#/components/schemas/Decimal","description":"`strbal`, Sage `Numeric(12,2)`."},"notes":{"type":"string","description":"`ntetxt`, a Sage memo field. **Published as `notes`, not `noteText`**. See the model note."},"inactive":{"type":"integer","format":"int16","description":"`inactv`, a `0`/`1` flag. **Published as `inactive`, not `isInactive`**. See the model note."}},"description":"`the underlying record`, the subaccount master (1-7 General Ledger Accounts).\n\nA subaccount partitions one controlling account, and the account/subaccount pair\nis what `the underlying record` posts against and what `the underlying record` carries balances for. This is\nthe master; `LedgerSubBalance` is the per-period figure.\n\n**The same three field names diverge here as on `LedgerAccount`, and are\npublished unchanged.** `ntetxt` is `notes` where 43 other models say\n`noteText`; `inactv` is `inactive` where every other model says `isInactive`;\nand `endbal` is `endBalance` where `the underlying record` publishes the same column as\n`endingBalance`."},"LedgerTransaction":{"type":"object","required":["id","recordNumber","transactionNumber","transactionDate","enteredDate","description","status","sourceNumber","period","year"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"transactionNumber":{"type":"string","description":"Caller-supplied. Not generated and not unique by anything either repo can see."},"transactionDate":{"type":"string","format":"date","description":"Date, in both directions."},"enteredDate":{"type":"string","format":"date"},"description":{"type":"string"},"status":{"$ref":"#/components/schemas/LedgerTransactionStatus"},"sourceNumber":{"type":"integer","format":"int32","description":"Install-specific above 33; query the transaction-source lookup."},"period":{"type":"integer","format":"int16","description":"`actprd`, the accounting period the row was posted into. v2 spelled this\nconcept inconsistently across its read and write models; v3 publishes it as\n`period` everywhere."},"year":{"type":"integer","format":"int16"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/LedgerTransactionLine"},"description":"Child collection `lines`, requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a ledger transaction, as read.\n\n`lines` is the only child collection on this entity. **v2 also advertised a\n`jobCosts` collection here; it was never populated**, and v3 does not declare\nit. Job costs carry the ``recnum`` they were posted alongside, so query\nthe job-cost entity by that value instead."},"LedgerTransactionInput":{"type":"object","required":["transactionNumber","transactionDate","enteredDate","description","sourceNumber","lines"],"properties":{"userName":{"type":"string","description":"The Sage user the posting is attributed to."},"transactionNumber":{"type":"string","minLength":1,"description":"The document number. **Caller-supplied and not validated for uniqueness by\nOPDC**, because uniqueness is a property of an upstream numbering discipline\nthat neither this service nor its callers can see. Reusing one replays the\nprior post when the payload is identical and fails when it is not."},"transactionDate":{"type":"string","format":"date","description":"Date-only, `YYYY-MM-DD`. Drives the posting period."},"enteredDate":{"type":"string","format":"date"},"description":{"type":"string","minLength":1},"status":{"$ref":"#/components/schemas/LedgerTransactionStatus","description":"The journal's status. Absent means `1`, Open.\n\nA coded field is a pointer in v3, so absent and `0` are different things: in\nv2 both serialized as `0`, which is not a member of any Sage status family,\nand every documented default was therefore unreachable.","default":1},"sourceNumber":{"type":"integer","format":"int32","minimum":1,"description":"The GL transaction source. `3` (Journal) for a journal entry.\nInstall-specific above 33; query the transaction-source lookup."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The posting period, 1-13.\n\n**Optional, and honoured when supplied**. That is the change 4.4 makes. Omit it and\nthe server derives the period from `transactionDate` under the installation's\n``prddte`` policy. Supply it and it must be a member of `validPeriods`\nas published by ledger-setup, or the request is a 422.\n\nIn v2 this field was accepted, documented as ignored, and ignored."},"year":{"anyOf":[{"type":"integer","format":"int16"},{"type":"null"}],"description":"The fiscal year to post into. Optional, and honoured when supplied, with the\nsame `validPeriods` check. Explicit `null` is accepted as equivalent to\nabsent."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/LedgerTransactionLineInput"},"minItems":1,"description":"At least one. Debits must equal credits across the whole journal."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostInput"},"description":"Job costs to write against this journal."}},"description":"A journal to post."},"LedgerTransactionLine":{"type":"object","required":["id","recordNumber","lineNumber","description","ledgerAccount","debitAmount","creditAmount"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"description":{"type":"string"},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber"},"subAccount":{"type":"integer","format":"int64"},"debitAmount":{"$ref":"#/components/schemas/Decimal"},"creditAmount":{"$ref":"#/components/schemas/Decimal"}},"description":"`the underlying record`, a ledger transaction line, as read."},"LedgerTransactionLineInput":{"type":"object","required":["lineNumber","ledgerAccount"],"properties":{"lineNumber":{"type":"integer","format":"int32","minimum":1},"description":{"type":"string","description":"Truncated to 30 characters on write."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber"},"subAccount":{"type":"integer","format":"int64"},"debitAmount":{"$ref":"#/components/schemas/Decimal","description":"Mutually exclusive with `creditAmount`. Exactly one of the two must be non-zero.","default":"0.00"},"creditAmount":{"$ref":"#/components/schemas/Decimal","description":"Mutually exclusive with `debitAmount`.","default":"0.00"}},"description":"One ledger transaction line to post."},"LedgerTransactionLineResult":{"type":"object","required":["id","recordNumber","lineNumber","description","ledgerAccount","debitAmount","creditAmount"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"description":{"type":"string","description":"Truncated to 30 characters on write by Sage."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber"},"subAccount":{"type":"integer","format":"int64"},"debitAmount":{"$ref":"#/components/schemas/Decimal"},"creditAmount":{"$ref":"#/components/schemas/Decimal"}},"description":"One ledger transaction line as written."},"LedgerTransactionResult":{"type":"object","required":["id","recordNumber","transactionNumber","status","sourceNumber","lines"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"transactionNumber":{"type":"string"},"status":{"$ref":"#/components/schemas/LedgerTransactionStatus"},"sourceNumber":{"type":"integer","format":"int32"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/LedgerTransactionLineResult"}}},"description":"A ledger transaction as written, header and lines."},"LedgerTransactionStatus":{"type":"integer","format":"int16","description":"``status``.\n\n**Status decides whether balances move.** `1` is the only value that propagates\nledger, period and sub-account balances; `2`, `3` and any other value are\nbehaviourally identical to each other.","x-enum-descriptions":["Open. Posted: account, period and sub-account balances are propagated.","Cleared. Set by Sage during bank reconciliation, not by a caller.","Void. Cannot be set directly; void the record in Sage instead."],"x-enum-varnames":["Open","Cleared","Void"],"enum":[1,2,3]},"LedgerTransactionWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun","period","year"],"properties":{"document":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into."}},"description":"The body of a successful `POST /ledger-transactions`.\n\nThe envelope's optional `ledgerTransaction` is **always absent here**: on this\none endpoint the document *is* the journal, so `document` and\n`ledgerTransaction` would be the same object under two names, and one write\nwould read as two. `inventory` is always empty; a journal moves no stock."},"LockRequest":{"type":"object","required":["userName"],"properties":{"userName":{"type":"string","description":"The user the lock is recorded against. Required.\n\nUnlocking checks this against the holder, so pass the name that took the\nlock or the unlock is refused with a `409`."}},"description":"Who is taking the lock."},"LockResult":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"`locked` or `unlocked`."},"lockUser":{"type":"string","description":"The user the lock is held for. Present on lock."},"response":{"description":"The on-premise agent's raw reply. Diagnostic; its shape is not contractual."}},"description":"The result of locking or unlocking the database."},"MatchFilter":{"type":"object","required":["operator"],"properties":{"field":{"type":"string","description":"Single-field match. Mutually exclusive with `fields`.","examples":["recordNumber"]},"fields":{"type":"array","items":{"type":"string"},"description":"Multi-field match. Required by, and only valid with, the `inPairs` operator."},"operator":{"$ref":"#/components/schemas/FilterOperator"},"value":{"description":"Single comparison value. Valid for every operator except `IN` and `inPairs`."},"values":{"type":"array","items":{},"description":"Comparison values. `IN` only."},"pairs":{"type":"array","items":{"type":"object","unevaluatedProperties":{}},"description":"One object per tuple, each key matching an entry in `fields`. `inPairs` only."}},"description":"A leaf comparison in a filter tree."},"MatchHitClient":{"type":"object","required":["id","recordNumber","distance"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): the client number a human reads in Sage.\n\nThis is the value other entities carry as `clientNumber` (`Job.clientNumber`,\nand eight further models on `clnnum`)."},"shortName":{"type":"string","description":"`shtnme`. The abbreviated name Sage shows in pick lists."},"clientName":{"type":"string","description":"`clnnme`."},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string"},"zipCode":{"type":"string"},"billingAddressLine1":{"type":"string","description":"`bilad1`, the billing address.\n\n**`billAddress1` in v2.** The whole billing group is renamed, so that the\nbilling address and the primary address are spelled the same way. The Sage\ncolumns are unchanged."},"billingAddressLine2":{"type":"string","description":"`bilad2`. `billAddress2` in v2; see `billingAddressLine1`."},"billingCityName":{"type":"string","description":"`bilcty`. `billCity` in v2."},"billingStateCode":{"type":"string","description":"`bilste`. `billState` in v2."},"billingZipCode":{"type":"string","description":"`bilzip`. `billZip` in v2."},"userDefined1":{"type":"string","description":"`usrdf1`. Free text, meaning defined per install."},"userDefined2":{"type":"string","description":"`usrdf2`. Free text, meaning defined per install."},"contact1":{"type":"string","description":"`contct`. The primary contact's name, free text.\n\n**The numbering is deliberate and is kept.** `the underlying record` is the one table that\ncarries three contacts, so `contact1` here and a bare `contact` on other\nentities are different data rather than an inconsistency. The same reasoning\nholds the `1` suffix on `phone1`, `fax1`, `cell1` and `email1` below, none of\nwhich has a modelled `2`."},"contact2":{"type":"string","description":"`contc2`. The second contact's name."},"contact3":{"type":"string","description":"`contc3`. The third contact's name."},"phone1":{"type":"string","description":"`phnnum`. Free text as Sage stores it; no format is imposed. See `contact1` on the suffix."},"fax1":{"type":"string","description":"`faxnum`."},"cell1":{"type":"string","description":"`cllphn`."},"email1":{"type":"string","description":"`e_mail`."},"employeeNumber":{"type":"integer","format":"int64","description":"`empnum`. The employee assigned to this client, in the salesperson role.\n`0` when unset.\n\n**`salesperson` in v2.** `empnum` was published under three role names across\nthe v2 read surface (`employeeNumber`, `salesperson`, `technician`); v3 gives\nthe column one name. `Job.salesperson` is **not** the same column (a job has\nits own `slsemp`), so it keeps its name."},"manager":{"type":"integer","format":"int64","description":"`mannum`. The managing employee's record number. `0` when unset.\n\nA different column from `empnum`, and unrelated to ``mannum``, which is\na manufacturer part number on the part master."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``: Sage's tax district table.\nInstall-specific and **not an enum**. It is never validated on write, so a\ncaller should check the number against the tax districts before posting it."},"discountTerms":{"type":"string","description":"`dsctrm`. A terms string, not a date."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"`dscrte`. JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"dueTerms":{"type":"string","description":"`duetrm`. A terms string, not a date."},"financeRate":{"$ref":"#/components/schemas/Decimal","description":"`finrte`. Finance charge rate."},"leadSource":{"type":"integer","format":"int32","description":"`ledsrc`. A foreign key into Sage's Lead Sources table.\n\nService inventory exposes the same kind of value (``invsrc``) as\n`sourceNumber` in v2, which collides with the GL transaction source and is\nrenamed to `leadSource` in v3. Install-specific, and the Lead Sources lookup\nis not exposed, so the code cannot currently be resolved to a name."},"clientType":{"type":"integer","format":"int16","description":"`clntyp`. Install-specific and **not an enum**. Query the client-type lookup\nrather than hardcoding values."},"status":{"type":"integer","format":"int16","description":"`status`. **Not a fixed enum.** Client statuses live in Sage's `clnsts`\nlookup and vary per install. No lookup endpoint models `clnsts` yet, so the\ncode cannot currently be resolved to a name; do not assume the\nOpen/Review/Dispute family that the document statuses use."},"beginningBalance":{"$ref":"#/components/schemas/Decimal","description":"`begbal`."},"endingBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`."},"noteText":{"type":"string","description":"`ntetxt`."},"statementEmail":{"type":"string","description":"`stmeml`. The address statements are sent to, distinct from `email1`."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`. A `0`/`1` flag despite the `is` prefix; Sage stores an integer and v3 does not reshape it."},"distance":{"type":"integer","format":"int32","description":"Levenshtein edit distance from `matchString`. Lower is closer."}},"description":"One matched row: the entity, plus how far it was from the search string."},"MatchHitEmployee":{"type":"object","required":["id","recordNumber","distance"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): the employee number a human reads in\nSage.\n\n**This is the value the rest of payroll carries as `employeeNumber`**:\n``empnum``, ``empnum``, and the write contract's\n`PayrollCheckInput.employeeNumber`, which is resolved against this table and\nfails the whole batch when it matches no employee. v3 spells this value\n`employeeNumber` on every entity that carries it."},"lastName":{"type":"string","description":"`lstnme`."},"firstName":{"type":"string","description":"`fstnme`."},"middleInitial":{"type":"string","description":"`midini`."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`.\n\n**Deliberately not modelled as an enum.** The meaning of the codes on this\nparticular table is not documented, and nothing in the API reads or writes\nit. In particular, do not assume the `1-Open`/`2-Review`/`3-Dispute` shape\nthat the AP and purchase-order statuses use: payroll and job statuses do\nnot participate in that family. Resolve the codes against Sage's own help\nbefore branching on them."},"addressLine1":{"type":"string","description":"`addrs1`. Home address. See the PII note on this model."},"addressLine2":{"type":"string","description":"`addrs2`."},"cityName":{"type":"string","description":"`ctynme`."},"stateCode":{"type":"string","description":"`state_`. Two-letter state or province code, as entered."},"zipCode":{"type":"string","description":"`zipcde`."},"phoneNumber":{"type":"string","description":"`phnnum`."},"cellPhone":{"type":"string","description":"`cllphn`."},"faxNumber":{"type":"string","description":"`faxnum`."},"homeNumber":{"type":"string","description":"`homnum`. The employee's home telephone number. See the PII note."},"email":{"type":"string","description":"`e_mail`. See the PII note."},"equipmentNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`eqpnum`, a foreign key to ``recnum`` (Equipment): the piece of\nequipment assigned to this employee. Reads `0` when none is."},"birthDate":{"type":"string","format":"date","description":"`dtebth`, the date of birth. A date, not an instant.\n\n**The single most sensitive column on the model.** Read the PII note above\nbefore adding a caller to this entity."},"hireDate":{"type":"string","format":"date","description":"`dtehre`, the hire date. A date, not an instant."},"inactiveDate":{"type":"string","format":"date","description":"`dteina`, the date the employee was made inactive. Distinct from `isInactive`, which is the flag."},"lastRaise":{"type":"string","format":"date","description":"`lstrse`, the date of the last raise. Not a modification stamp; it says nothing about when the row changed."},"gender":{"type":"integer","format":"int16","description":"`gender`, Sage `Numeric(1)`.\n\nNot modelled as an enum: which integer means what is not established\nanywhere this API can verify. Query Sage's own screen if a caller needs it."},"positionNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`paypst`, a foreign key to ``recnum`` (the `PayrollPosition` entity).\n\nNamed `position` in v2. It is spelled `positionNumber` here because a field\ncarrying another table's record number ends in `Number` throughout v3. No\nother model publishes this column, so there is only ever one spelling of it."},"compCode":{"$ref":"#/components/schemas/RecordNumber","description":"`wrkcmp`, a foreign key to ``recnum`` (the `WorkersComp` entity): the\nemployee's default workers' compensation code.\n\n**Published as `compCode`, without a `Number` suffix**, on the precedent of\n`costCode`: ``recnum`` is a foreign key too and ships as `costCode` on\nevery model that carries it. \"Comp code\" is Sage's own term for this value.\n\nNote the column: `TimecardLine` and `DailyPayroll` reach the same lookup\nthrough `cmpcde`, a **different column** published under this same field\nname."},"taxState":{"type":"string","description":"`taxste`. The state whose withholding tables apply; copied onto ``taxste`` per check."},"payPeriod":{"type":"integer","format":"int16","description":"`payprd`, the pay-period **frequency code** (weekly, bi-weekly, semi-monthly,\nmonthly): an integer code, not a date.\n\n**``payprd`` is the same column name on a different table holding the\nperiod END DATE**, published there as `periodEnd: plainDate`. One column\nname, two types, two meanings. Reading this field as a date is the obvious\nmistake and it is why both fields are annotated.\n\nThe frequency codes themselves are not enumerated by this API, so this is\nnot typed as an enum."},"paygroup":{"$ref":"#/components/schemas/RecordNumber","description":"`paygrp`, a foreign key to ``recnum`` (the `Paygroup` entity).\n\nPublished as `paygroup`, without a `Number` suffix, consistently with every\nother model in the API that carries this column."},"regularRate":{"$ref":"#/components/schemas/Decimal","description":"`payrt1`, the master regular rate. See the PII note.\n\nOne of the three columns a posted payroll batch is reconciled against: an\nearning of pay type `1-Regular` whose rate differs from this by more than\nthe batch's `rateTolerance` fails the whole batch."},"overtimeRate":{"$ref":"#/components/schemas/Decimal","description":"`payrt2`, the master overtime rate. Reconciled against pay type `2-Overtime`."},"premiumRate":{"$ref":"#/components/schemas/Decimal","description":"`payrt3`, the master premium (double-time) rate. Reconciled against pay type\n`3-Premium`.\n\n**Rate parity stops here.** Pay types 4 through 9 have no master rate column\nto check against (`the underlying record` has exactly these three), so earnings of those\ntypes are not rate-checked at all."},"salaryAmount":{"$ref":"#/components/schemas/Decimal","description":"`salary`, the salaried employee's per-period salary. See the PII note.\n\n**Named `salary` in v2, published as `salaryAmount` here.** `the underlying record`\npublishes the same column as `salaryAmount` already, so the two entities now\nagree; a money field carries the `Amount` suffix throughout v3."},"userDefined1":{"type":"string","description":"`usrdf1`."},"userDefined2":{"type":"string","description":"`usrdf2`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag Sage stores as a smallint.\n\nNot a `boolean`: the wire carries `0`/`1` and the read path does not\nconvert. Every `inactv` flag in this API is published the same way."},"distance":{"type":"integer","format":"int32","description":"Levenshtein edit distance from `matchString`. Lower is closer."}},"description":"One matched row: the entity, plus how far it was from the search string."},"MatchHitJob":{"type":"object","required":["id","recordNumber","distance"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): the job number a human reads in Sage."},"jobName":{"type":"string","description":"`jobnme`."},"shortName":{"type":"string","description":"`shtnme`. The abbreviated name Sage shows in pick lists."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clnnum`. The client this job bills to; ``recnum``, the `Client` entity's `recordNumber`."},"contact":{"type":"string","description":"`contct`. A person's name, free text. Not a foreign key."},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string"},"zipCode":{"type":"string"},"phoneNumber":{"type":"string","description":"`phnnum`. Free text as Sage stores it; no format is imposed."},"faxNumber":{"type":"string"},"architect":{"type":"integer","format":"int64","description":"`achtct`. A record number into the architect lookup. `0` when unset.\n\nInstall-specific target; not an enum and not resolvable from this response."},"lender":{"type":"integer","format":"int64","description":"`lender`. A record number into the lender lookup. `0` when unset."},"supervisor":{"type":"integer","format":"int64","description":"`sprvsr`. The supervising employee's record number. `0` when unset."},"salesperson":{"type":"integer","format":"int64","description":"`slsemp`. The selling employee's record number. `0` when unset.\n\nDistinct column from ``empnum``, which v3 spells `employeeNumber` on\n`Client`; a job carries dedicated salesperson, estimator and supervisor\ncolumns, so each keeps its role name here."},"estimator":{"type":"integer","format":"int64","description":"`estemp`. The estimating employee's record number. `0` when unset."},"userDefined1":{"type":"string","description":"`usrdf1`. Free text, meaning defined per install."},"userDefined2":{"type":"string","description":"`usrdf2`. Free text, meaning defined per install."},"payrollLocale":{"type":"integer","format":"int32","description":"`lcltax`. A record number into the payroll local-tax (locale) table. Install-specific; not an enum."},"salesTaxDistrict":{"type":"integer","format":"int32","description":"`slstax`.\n\n**The meaning of this column on `the underlying record` is uncertain.** On every other table\nthat exposes `slstax` it is a currency amount, published as `salesTax` or\n`salesTaxAmount`; here it is named and typed as a sales-tax district code.\nVerify against a live row before depending on either the name or the type."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lgract`. The job's income account.\n\n**This field was named `incomeAccount` in v2.** v3 publishes the column as\n`ledgerAccount`, the spelling used for it everywhere else in this API."},"department":{"type":"integer","format":"int64","description":"`dptmnt`. A record number into the department table. `0` when unset."},"discountTerms":{"type":"string","description":"`dscdte`. A **terms string** on `the underlying record`, not a date.\n\nThe same column carries a date on the invoice tables, where it is published\nas `discountDate`. Do not assume `YYYY-MM-DD` here."},"dueTerms":{"type":"string","description":"`duedte`. A terms string on `the underlying record`, not a date. See `discountTerms`."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"`dsccnt`. JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"financeRate":{"$ref":"#/components/schemas/Decimal","description":"`finchg`. Finance charge rate."},"retainageRate":{"$ref":"#/components/schemas/Decimal","description":"`retain`. Retainage rate held back from billings."},"contractAmount":{"$ref":"#/components/schemas/Decimal","description":"`cntrct`. The contract amount."},"beginningBalance":{"$ref":"#/components/schemas/Decimal","description":"`begbal`."},"endingBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`."},"bidDate":{"type":"string","format":"date","description":"`biddte`. Date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage date column."},"contractDate":{"type":"string","format":"date","description":"`ctcdte`. Date, not an instant."},"startDate":{"type":"string","format":"date","description":"`sttdte`. Date, not an instant."},"completionDate":{"type":"string","format":"date","description":"`cmpdte`. Date, not an instant."},"jobType":{"type":"integer","format":"int16","description":"`jobtyp`, a foreign key to ``recnum``. Install-specific, so the codes\nare not enumerated in this contract; query the job-type lookup rather than\nhardcoding values."},"status":{"$ref":"#/components/schemas/JobStatus","description":"`status`. Job status does not restrict posting. See `JobStatus`."},"postToWip":{"type":"integer","format":"int16","description":"`pstwip`. A `0`/`1` flag, not a boolean on the wire: whether the job posts to work in progress."},"certifiedPayroll":{"type":"integer","format":"int16","description":"`crtfid`. A `0`/`1` flag: whether the job requires certified payroll reporting."},"contractNumber":{"type":"string","description":"`connum`. **Free text, not a record number**, despite the `Number` suffix:\nthe contract identifier as the customer writes it."},"clientPO":{"type":"string","description":"`pchord`. **Free text**: the client's own purchase-order number for this job. Not a `the underlying record` foreign key."},"noteText":{"type":"string","description":"`ntetxt`."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`. A `0`/`1` flag despite the `is` prefix; Sage stores an integer and v3 does not reshape it."},"distance":{"type":"integer","format":"int32","description":"Levenshtein edit distance from `matchString`. Lower is closer."}},"description":"One matched row: the entity, plus how far it was from the search string."},"MatchHitPart":{"type":"object","required":["id","recordNumber","distance"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\n**This is what write endpoints call `partNumber`**, a name that means the\nrecord number, not the code a human reads. In v3 the record number is\n`recordNumber` (or `partRecordNumber` where it is a foreign key on another\nentity) and the human-facing code is `partCode`."},"partCode":{"type":"string","description":"The alphanumeric part code a human reads (`alpnum`). `alphaNumber` in v2."},"partName":{"type":"string","description":"`prtnme`."},"partUnit":{"type":"string","description":"Unit of measure, `prtunt`."},"binNumber":{"type":"string","description":"`binnum`."},"manufacturer":{"type":"string"},"manufPartNumber":{"type":"string"},"userDefined1":{"type":"string"},"userDefined2":{"type":"string"},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Not an enum; query the cost-code lookup."},"costType":{"type":"integer","format":"int16","description":"Install-specific above 5. Presets are 1 Material, 2 Labor, 3 Equipment,\n4 Subcontract, 5 Other; 6-9 are defined per installation. Query the cost-type\nlookup rather than assuming."},"partClassNumber":{"type":"integer","format":"int64","description":"Foreign key into the part-class lookup (`prtcls`); see `PartClass`."},"defaultLocation":{"type":"integer","format":"int32"},"lastUpdated":{"type":"string","format":"date","description":"Date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage `Date`\ncolumn. One of the two `lstupd` columns in the product, so this is one of the\nseven entities where a `lastUpdated` filter is honoured rather than answered\nwith a 501."},"reorderQuantity":{"$ref":"#/components/schemas/Decimal","description":"JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"minOrderQty":{"$ref":"#/components/schemas/Decimal"},"packageQuantity":{"$ref":"#/components/schemas/Decimal"},"unitWeight":{"$ref":"#/components/schemas/Decimal"},"averageCost":{"$ref":"#/components/schemas/Decimal"},"partCost":{"$ref":"#/components/schemas/Decimal","description":"`prtcst`, the part's own cost. Not the same column as `averageCost`."},"billingAmount":{"$ref":"#/components/schemas/Decimal","description":"`prtbil`, the billing amount. `AssemblyPart` publishes this same column as `billingAmount` too."},"quantityOnHand":{"$ref":"#/components/schemas/Decimal"},"stockItem":{"type":"boolean"},"serialized":{"type":"integer","format":"int16","description":"`serinv`, a 0/1 flag Sage stores as a smallint. See `isInactive`."},"markupPercent":{"$ref":"#/components/schemas/Decimal"},"noteText":{"type":"string"},"isInactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag stored as a smallint.\n\n**An `is`-prefixed name over an integer is misleading and it is deliberate\nhere**: the wire carries `0`/`1`, not `false`/`true`, and no conversion is\ndone on read. Renaming the field without converting the value would be worse."},"distance":{"type":"integer","format":"int32","description":"Levenshtein edit distance from `matchString`. Lower is closer."}},"description":"One matched row: the entity, plus how far it was from the search string."},"MatchHitVendor":{"type":"object","required":["id","recordNumber","distance"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\nThis is the value every other entity carries as `vendorNumber`. v2 spelled\nthat foreign key `vendorRecordNumber` on purchase order alone; v3 uses\n`vendorNumber` everywhere."},"vendorName":{"type":"string"},"shortName":{"type":"string"},"ownerName":{"type":"string"},"contact":{"type":"string"},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string","description":"`state_`. Two-letter state or province code, as entered."},"zipCode":{"type":"string"},"phoneNumber":{"type":"string"},"faxNumber":{"type":"string"},"cellPhone":{"type":"string"},"email":{"type":"string"},"federalIdNumber":{"type":"string"},"stateIdNumber":{"type":"string"},"licenseNumber":{"type":"string"},"accountNumber":{"type":"string","description":"The account number this company holds **with** the vendor, free text.\n\nNot a ledger account. Sage stores it in `actnum`, which is also the column\nthe underlying record uses for a GL account number, and v2 published both as\n`accountNumber`: one field name over two unrelated types. The invoice line\nis renamed `ledgerAccount` in v3; this one keeps the name, because here it\nreally is an account number at the vendor."},"userDefined1":{"type":"string","description":"`usrdf1`.\n\n**Spelled `userDefinedField1` on this model alone in v2.** Every other model\nthat exposes the column calls it `userDefined1`; the underlying record was the outlier, and\nv3 brings it into line."},"userDefined2":{"type":"string","description":"`usrdf2`. Spelled `userDefinedField2` in v2; see `userDefined1`."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"discountDateTerms":{"type":"string","description":"Payment terms text, **not a date**, despite the column being `dscdte`.\n\nSage reuses `dscdte`/`duedte` for a date on invoices and for a terms string\non the vendor and job masters, which is why this is a `string` here and a\n`plainDate` on `PayableInvoice.discountDate`."},"dueDateTerms":{"type":"string","description":"Payment terms text, not a date (`duedte`). See `discountDateTerms`."},"ledgerDefault":{"type":"integer","format":"int64","description":"The vendor's default GL account (`lgrdft`), carried onto a new payable\ninvoice by Sage.\n\nA vendor with no default reads back `0` rather than being omitted, so this\nfield is a plain integer and is not constrained to a valid account number."},"creditDefault":{"$ref":"#/components/schemas/Decimal","description":"`cdedft`. Meaning not established beyond Sage's column name; no documented value set."},"typeDefault":{"type":"integer","format":"int16","description":"Defaults Sage carries onto a new record for this vendor (`typdft`,\n`stsdft`, `wrndft`).\n\nLeft as plain integers deliberately. `typdft` and `stsdft` resemble the\n`ApInvoiceType` and `ApInvoiceStatus` families, but Sage's published help\ndoes not confirm that the underlying record draws from them, so they are not typed as those\nenums. Read the meanings off the install."},"statusDefault":{"type":"integer","format":"int16","description":"See `typeDefault`. Not typed as `ApInvoiceStatus`; the link is unverified."},"warrantyDefault":{"type":"integer","format":"int16","description":"See `typeDefault`. `wrndft`; no documented value set."},"beginningBalance":{"$ref":"#/components/schemas/Decimal"},"endingBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`. the underlying record and the underlying record publish the same column as `endBalance`; the underlying record's spelling is the majority one."},"vendorType":{"type":"integer","format":"int16","description":"`vndtyp`, a foreign key to ``recnum``. Install-configurable; no enum. Read the labels from the vendor-type table."},"minorityType":{"type":"integer","format":"int16","description":"`minsts`, minority status. Coded; no Sage source found. No enum."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``: Sage's tax district table.\n**Not an enum**; districts are configured per install."},"compRate":{"$ref":"#/components/schemas/Decimal","description":"`cmprte`. A rate, not an amount."},"useTaxRate":{"$ref":"#/components/schemas/Decimal","description":"`utxrte`. A rate, not an amount, unlike `PayableInvoice.useTaxRate`, whose column is `usetax`."},"orderType":{"type":"integer","format":"int16","description":"`ordtyp`. Coded; no Sage source found. No enum."},"orderDescription":{"type":"string"},"subcontractType":{"type":"integer","format":"int16","description":"`contyp`, the vendor's default subcontract type.\n\nThe same column is published as `contractType` on the subcontract itself,\nwhich is different data rather than an inconsistency. Coded; no Sage source\nfound."},"accountType":{"type":"integer","format":"int16","description":"`acttyp`. Coded; no Sage source found. No enum."},"print1099":{"type":"integer","format":"int16","description":"Flags Sage stores as integers, not booleans, and which arrive on the wire as\nintegers: `prt199` (issue a 1099), `intrnl` (an internal vendor),\n`dirdep` (ACH / direct deposit is set up), `inactv` (the vendor is retired).\n\nDo not assume the values are limited to `0` and `1`: `print1099` in\nparticular is a 1099 **box** code in some Sage versions."},"internalVendor":{"type":"integer","format":"int16","description":"See `print1099`. `intrnl`."},"achEnabled":{"type":"integer","format":"int16","description":"See `print1099`. The column is `dirdep`, direct deposit; v2 exposes it as `achEnabled`."},"isInactive":{"type":"integer","format":"int16","description":"See `print1099`. `inactv`. the underlying record and the underlying record publish the same column as `inactive`."},"returnNumber":{"type":"string","description":"`rtnmbr`. A free-text reference, not a Sage record number."},"bankAccount":{"type":"string"},"achEmail":{"type":"string","description":"`acheml`. Where remittance advice for an ACH payment is sent."},"noteText":{"type":"string","description":"`ntetxt`. Most entities publish this column as `noteText`; the underlying record and the underlying record say `notes`."},"distance":{"type":"integer","format":"int32","description":"Levenshtein edit distance from `matchString`. Lower is closer."}},"description":"One matched row: the entity, plus how far it was from the search string."},"MatchRequest":{"type":"object","required":["select","matchFields","matchString","maxDistance"],"properties":{"select":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Fields to return. **Required here**, unlike on `query`, and the server\nrejects an empty list."},"matchFields":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Fields to compute the distance against. Every one is scored and the smallest\ndistance wins, so a client can match a name against both `name` and\n`shortName` in one call.","examples":[["name","shortName"]]},"matchString":{"type":"string","minLength":1,"description":"The string to match against.","examples":["Acme Corp"]},"maxDistance":{"type":"integer","format":"int32","minimum":1,"description":"Largest Levenshtein edit distance to include, and **it is required**. A\nrequest that omits it, or sends zero, is rejected.","examples":[3]},"filters":{"$ref":"#/components/schemas/FilterNode","description":"Optional pre-filter to narrow the candidate set before scoring."}},"description":"A fuzzy-match request. Levenshtein distance over one or more fields.\n\nFive entities support this (`client`, `vendor`, `job`, `part`, `employee`). It\nis a different operation from `query`, not a filter operator, because it ranks\nrather than filters and it returns a score."},"MatchResponseClient":{"type":"object","required":["asOf","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock, as on `QueryResponse`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/MatchHitClient"},"description":"Sorted by `distance` ascending: best matches first."}},"description":"A fuzzy-match response.\n\n**No `page` block, deliberately.** Matching scores every candidate row before\nit can rank them, so it is not pageable in the way `query` is; `maxDistance` is\nthe bound. Stated here because v2's match envelope carried the same `page` key\nas query and never populated it."},"MatchResponseEmployee":{"type":"object","required":["asOf","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock, as on `QueryResponse`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/MatchHitEmployee"},"description":"Sorted by `distance` ascending: best matches first."}},"description":"A fuzzy-match response.\n\n**No `page` block, deliberately.** Matching scores every candidate row before\nit can rank them, so it is not pageable in the way `query` is; `maxDistance` is\nthe bound. Stated here because v2's match envelope carried the same `page` key\nas query and never populated it."},"MatchResponseJob":{"type":"object","required":["asOf","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock, as on `QueryResponse`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/MatchHitJob"},"description":"Sorted by `distance` ascending: best matches first."}},"description":"A fuzzy-match response.\n\n**No `page` block, deliberately.** Matching scores every candidate row before\nit can rank them, so it is not pageable in the way `query` is; `maxDistance` is\nthe bound. Stated here because v2's match envelope carried the same `page` key\nas query and never populated it."},"MatchResponsePart":{"type":"object","required":["asOf","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock, as on `QueryResponse`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/MatchHitPart"},"description":"Sorted by `distance` ascending: best matches first."}},"description":"A fuzzy-match response.\n\n**No `page` block, deliberately.** Matching scores every candidate row before\nit can rank them, so it is not pageable in the way `query` is; `maxDistance` is\nthe bound. Stated here because v2's match envelope carried the same `page` key\nas query and never populated it."},"MatchResponseVendor":{"type":"object","required":["asOf","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock, as on `QueryResponse`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/MatchHitVendor"},"description":"Sorted by `distance` ascending: best matches first."}},"description":"A fuzzy-match response.\n\n**No `page` block, deliberately.** Matching scores every candidate row before\nit can rank them, so it is not pageable in the way `query` is; `maxDistance` is\nthe bound. Stated here because v2's match envelope carried the same `page` key\nas query and never populated it."},"OnPremiseBuild":{"type":"object","required":["version","reported","presumedOld"],"properties":{"version":{"type":"string","description":"The on-premise build, or `unknown`."},"reported":{"type":"boolean","description":"Whether the service reported its version at all.\n\n**Read `false` as \"old\", not as \"no information\".** A service predating\nversion reporting cannot report a version, so silence is evidence of an old\nbuild. A release check must treat `false` as not-yet-upgraded."},"presumedOld":{"type":"boolean","description":"The same fact as `reported`, stated so it does not have to be inverted."}},"description":"The on-premise Windows service's build, as it reported it."},"PageRequest":{"type":"object","properties":{"pageNumber":{"type":"integer","format":"int32","minimum":1,"description":"**1-indexed.** Page 1 is the first page.\n\nThe public contract uses the numbering people expect in a UI. The\nexecutor's internal zero-based offset is an implementation detail.","default":1},"pageSize":{"type":"integer","format":"int32","minimum":1,"maximum":1000,"description":"Rows per page. **Defaults to 100** when the whole `page` block is omitted, so\n\"fetch everything\" quietly returns the first hundred rows unless you read\n`page.totalCount`. The default is reported back on every response so a client\ncan see what it got without knowing the server's defaults.\n\n**The maximum is 1000 and a request above it is rejected, never clamped**\n(400, `FIELD_INVALID`). Clamping is the same failure class as v2's silent\n`lastUpdated` no-op: the caller believes it asked for and received 5000 rows.\nWalk the rest with `pageNumber` and `page.totalCount`.\n\n**`pageSize: 0` is a 400, not \"everything\".** In v2 any value below 1 fell\nthrough to an unbounded fetch on any of 79 entities. There is no way to ask\nfor an unbounded result set; page through it.","default":100}},"description":"Paging request.\n\n**Two v2 fields are gone.** `offset` was accepted as an alternative to\n`pageNumber`; `count` was accepted on the way in and ignored, and it is output\nonly, spelled `totalCount` on the way out. Sending either now returns 400 under\nv3's unknown-field rejection rather than being silently ignored."},"PageResponse":{"type":"object","required":["pageNumber","pageSize","totalCount","returnedCount"],"properties":{"pageNumber":{"type":"integer","format":"int32","description":"Echoed from the request, or `1`. 1-indexed, as on the way in."},"pageSize":{"type":"integer","format":"int32","description":"The page size in effect, including when the server applied its default of 100."},"totalCount":{"type":"integer","format":"int32","description":"Total rows matching the filter. This is v2's `page.count`, renamed rather\nthan redefined: `count` was the total, not the size of the page returned,\nand every integrator had to be told so."},"returnedCount":{"type":"integer","format":"int32","description":"Rows in `data` on this response. Never larger than `pageSize`, and smaller\non the last page."}},"description":"Paging as reported back. Every field is always present, so a caller can see\nwhat it actually got without knowing the server's defaults."},"Part":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\n**This is what write endpoints call `partNumber`**, a name that means the\nrecord number, not the code a human reads. In v3 the record number is\n`recordNumber` (or `partRecordNumber` where it is a foreign key on another\nentity) and the human-facing code is `partCode`."},"partCode":{"type":"string","description":"The alphanumeric part code a human reads (`alpnum`). `alphaNumber` in v2."},"partName":{"type":"string","description":"`prtnme`."},"partUnit":{"type":"string","description":"Unit of measure, `prtunt`."},"binNumber":{"type":"string","description":"`binnum`."},"manufacturer":{"type":"string"},"manufPartNumber":{"type":"string"},"userDefined1":{"type":"string"},"userDefined2":{"type":"string"},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Not an enum; query the cost-code lookup."},"costType":{"type":"integer","format":"int16","description":"Install-specific above 5. Presets are 1 Material, 2 Labor, 3 Equipment,\n4 Subcontract, 5 Other; 6-9 are defined per installation. Query the cost-type\nlookup rather than assuming."},"partClassNumber":{"type":"integer","format":"int64","description":"Foreign key into the part-class lookup (`prtcls`); see `PartClass`."},"defaultLocation":{"type":"integer","format":"int32"},"lastUpdated":{"type":"string","format":"date","description":"Date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage `Date`\ncolumn. One of the two `lstupd` columns in the product, so this is one of the\nseven entities where a `lastUpdated` filter is honoured rather than answered\nwith a 501."},"reorderQuantity":{"$ref":"#/components/schemas/Decimal","description":"JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"minOrderQty":{"$ref":"#/components/schemas/Decimal"},"packageQuantity":{"$ref":"#/components/schemas/Decimal"},"unitWeight":{"$ref":"#/components/schemas/Decimal"},"averageCost":{"$ref":"#/components/schemas/Decimal"},"partCost":{"$ref":"#/components/schemas/Decimal","description":"`prtcst`, the part's own cost. Not the same column as `averageCost`."},"billingAmount":{"$ref":"#/components/schemas/Decimal","description":"`prtbil`, the billing amount. `AssemblyPart` publishes this same column as `billingAmount` too."},"quantityOnHand":{"$ref":"#/components/schemas/Decimal"},"stockItem":{"type":"boolean"},"serialized":{"type":"integer","format":"int16","description":"`serinv`, a 0/1 flag Sage stores as a smallint. See `isInactive`."},"markupPercent":{"$ref":"#/components/schemas/Decimal"},"noteText":{"type":"string"},"isInactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag stored as a smallint.\n\n**An `is`-prefixed name over an integer is misleading and it is deliberate\nhere**: the wire carries `0`/`1`, not `false`/`true`, and no conversion is\ndone on read. Renaming the field without converting the value would be worse."}},"description":"`the underlying record`, the part master.\n\nOne of only two entities in the product carrying a real modification stamp\n(`lstupd`), so it is one of the few that can be filtered on `lastUpdated`\nrather than on `createdSince`.\n\n**Only `id` and `recordNumber` are required.** Every other field is optional\nbecause a request may narrow the response with `select`; a row carrying three\nfields is the normal case, not a truncated one."},"PartClass":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Class#\". The value `Part.partClassNumber` carries."},"className":{"type":"string","description":"`clsnme`."},"indentLevel":{"type":"integer","format":"int16","description":"`indent`, Sage's \"Indent\". The node's depth in the class tree, for display."},"parentClass":{"type":"integer","format":"int64","description":"`parcls`, Sage's \"Class Parent#\", a self-referencing foreign key to ``recnum``. `0` at the root."},"hasChildren":{"type":"integer","format":"int16","description":"`haskid`, Sage's \"Class Has Children\". A `0`/`1` flag stored as a smallint, not a boolean on the wire."}},"description":"`the underlying record`, the part-class lookup (9-4 Part Classes).\n\n**This is the table `Part.partClassNumber` points at**, through ``recnum``.\nPart classes are install-specific, so none is ever named in the spec; this\nendpoint resolves the code.\n\nThe table is a **tree**, not a flat list: `parentClass` and `indentLevel` carry\nthe hierarchy and `hasChildren` marks the interior nodes. Nothing in the API\nwalks it for you; a caller wanting the tree queries the whole table and\nassembles it.\n\nAll four of Sage's columns are read, which makes this one of the few lookups\nwith no coverage gap."},"PayEarningType":{"type":"integer","format":"int16","description":"`the underlying record` / `the underlying record` earning pay type. Confidence: verified, cited by the\nscript against Sage 100 Contractor 5-2-2, \"About pay types\".\n\n1-6 are hourly: each rolls into its own `the underlying record` hours/pay bucket and into\n`ttlhrs`. 7-Piece is paid per piece: its quantity is a piece count\n(``pieces``), not hours, and its pay lands in ``pcerte``.\n8-Per-Diem and 9-Miscellaneous are lump sums with no quantity at all,\nlanding in ``perdim`` and ``mscpay`` respectively. Because 7, 8\nand 9 carry no hours, `ttlhrs` always equals the sum of the six hourly\nbuckets. Any other value is rejected before any write.\n\n**Not the same field as `PayrollCheckInput.payType`** (``paytyp``,\nthe check's own \"computer check\" type code) despite the identical name in\nthe v2 DTO: two different columns on two different tables, one caller\nvalue each. Flagged for review, not resolved here.","x-enum-varnames":["Regular","Overtime","Premium","Sick","Vacation","Holiday","Piece","PerDiem","Miscellaneous"],"enum":[1,2,3,4,5,6,7,8,9]},"PayableInvoice":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"invoiceNumber":{"type":"string","description":"`invnum`. The vendor's own invoice number, free text, not a Sage record number."},"purchaseOrder":{"type":"string","description":"`pchord`. The purchase order reference as it appears on the vendor's\ninvoice: free text, **not** a link to a the underlying record row and not a record number.\n\nSage puts three different roles in this column across the read surface:\n`clientPO` on a job, `orderNumber` on a ledger transaction, this one on a\npayable invoice. They are genuinely different data, so the names correctly\ndiffer."},"subcontractCode":{"type":"string","description":"`ctcnum`, the subcontract this invoice bills against.\n\n**`subcontractNum` in v2**, an abbreviation Sage does not use. The value is\na free-text code rather than a record number, and the subcontract entity\npublishes the same column as `contractNumber`."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, the vendor's `recordNumber`."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`. Absent when the invoice is not job-costed."},"phaseNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`phsnum`. The job phase."},"description":{"type":"string"},"invoiceDate":{"type":"string","format":"date","description":"Date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"dueDate":{"type":"string","format":"date"},"discountDate":{"type":"string","format":"date","description":"The date the early-payment discount expires. A real date here, unlike `Vendor.discountDateTerms`."},"enteredDate":{"type":"string","format":"date","description":"The date the invoice was entered. Sage derives the posting period from it."},"referenceNumber":{"type":"string"},"invoiceType":{"$ref":"#/components/schemas/ApInvoiceType"},"status":{"$ref":"#/components/schemas/ApInvoiceStatus"},"payee2":{"type":"string","description":"`payee2`. A second payee line, Sage's own name for the field."},"userDefined1":{"type":"string"},"userDefined2":{"type":"string"},"discountAmount":{"$ref":"#/components/schemas/Decimal","description":"`dscavl`, the discount available. JSON string, native scale, as every decimal here is."},"discountTaken":{"$ref":"#/components/schemas/Decimal","description":"`dsctkn`, the discount actually taken.\n\n**`discCredit` in v2**, an abbreviation Sage does not use. `discountTaken`\nis the spelling the payment entities already publish for this column."},"retention":{"$ref":"#/components/schemas/Decimal"},"setToPay":{"$ref":"#/components/schemas/Decimal"},"amountPaid":{"$ref":"#/components/schemas/Decimal"},"totalPaid":{"$ref":"#/components/schemas/Decimal"},"subtotalAmount":{"$ref":"#/components/schemas/Decimal"},"freight":{"$ref":"#/components/schemas/Decimal"},"invoiceTotal":{"$ref":"#/components/schemas/Decimal","description":"`invttl`."},"invoiceNet":{"$ref":"#/components/schemas/Decimal","description":"`invnet`."},"invoiceAmount":{"$ref":"#/components/schemas/Decimal","description":"`invamt`."},"invoiceBalance":{"$ref":"#/components/schemas/Decimal","description":"`invbal`. What remains unpaid."},"variance":{"$ref":"#/components/schemas/Decimal","description":"`adjust`. Exposed as `variance` in v2; the column name says adjustment, and the two do not obviously mean the same thing."},"holdbackAmount":{"$ref":"#/components/schemas/Decimal"},"holdbackBilled":{"$ref":"#/components/schemas/Decimal"},"holdbackRemain":{"$ref":"#/components/schemas/Decimal"},"compAmount":{"$ref":"#/components/schemas/Decimal","description":"`cmpamt`."},"useTaxRate":{"$ref":"#/components/schemas/Decimal","description":"`usetax`.\n\nPublished as `useTaxRate`, but the column is named for an amount and it sits\namong amounts. `Vendor.useTaxRate` is `utxrte`, which is unambiguously a\nrate. Confirm which this is before depending on it."},"period":{"type":"integer","format":"int16","description":"The period the invoice posted into (`actper`)."},"year":{"type":"integer","format":"int16","description":"`postyr`."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the the underlying record this invoice posted, or `0` when\nno journal was written, which is every status other than `1-Open`\n(see `ApInvoiceStatus`).\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason."},"voidRecords":{"type":"array","items":{"type":"integer","format":"int64"},"description":"`vodrec`. The records that voided this invoice. Absent when it was not\nvoided.\n\n**A list here, and a scalar on every other entity that publishes `vodrec`.**\nThat is not an inconsistency for its own sake: `the underlying record`, `the underlying record` and\n`the underlying record` store `vodrec` as a `bigint` holding exactly one value, while\n`the underlying record` stores it as `nvarchar(300)` holding a pipe-delimited list,\n\"990|1552\". A scalar cannot represent that without dropping voiding records,\nso this model publishes what the column can actually hold.\n\nThe delimiter never reaches a caller; the values arrive already split and\nparsed. Not typed `RecordNumber` (`@minValue(1)`) because the underlying\nvalues are not constrained that way."},"userName":{"type":"string"},"batchNumber":{"type":"integer","format":"int32"},"shipmentNumber":{"type":"string","description":"`shpnum`. A free-text shipment reference, not a Sage record number."},"noteText":{"type":"string","description":"`ntetxt`."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PayableInvoiceLine"},"description":"Child collection `lines`, requested via `children: [\"lines\"]`. the underlying record rows,\nkeyed `_idref` → the parent's `_idnum`.\n\nTwo behaviours to plan for: children ignore `select`, so every column of\nevery line comes back regardless of what was asked for on the parent, and\nchildren are unpaginated and unfilterable."}},"description":"`the underlying record`, a payable invoice, as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read request may narrow the response with `select`, in\nwhich case a column that was not asked for is absent from the row entirely.\nA column that is NULL in Sage currently comes back as JSON `null` rather than\nbeing omitted.\n\nDates here are dates. v2 read them back as RFC 3339 instants\n(`\"2026-01-15T00:00:00Z\"`) while the write endpoint took and returned\n`YYYY-MM-DD`, so an `enteredDate` read off an invoice could not be posted back\nas one."},"PayableInvoiceLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `PayableInvoice`.\n\nReturned on every child row: the child SELECT is built from the full column\nmap and ignores `select`, so this comes back even when the row is already\nnested under the parent that it names."},"lineNumber":{"type":"integer","format":"int32"},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, the part's Sage record number.\n\n**`partNumber` in v2**, a name that reads as the human-facing part code and\nis not one. Matches `Part.recordNumber`; the code a human reads is\n`Part.partCode`."},"partCode":{"type":"string","description":"The alphanumeric part code a human reads (`alpnum`). `alphaNumber` in v2; see `partRecordNumber`."},"partDescription":{"type":"string"},"unitDescription":{"type":"string"},"lineQuantity":{"$ref":"#/components/schemas/Decimal","description":"JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"linePrice":{"$ref":"#/components/schemas/Decimal","description":"The unit price, JSON string at native scale.\n\nv2's **write** response rounded this to two places, so a line posted at\n`36.303` echoed back as `36.30` while the value stored in Sage (and read\nback here) kept all three. v3 has one encoding in both directions and does\nno rounding."},"extendedTotal":{"$ref":"#/components/schemas/Decimal"},"holdbackAmount":{"$ref":"#/components/schemas/Decimal"},"invoiceAmount":{"$ref":"#/components/schemas/Decimal"},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"The GL expense account the line is charged to (`actnum`).\n\n**`accountNumber` in v2.** Renamed because the ledger transaction line\nalready publishes this same concept as `ledgerAccount`, and because v2's\n`accountNumber` named both this integer GL account and\n`Vendor.accountNumber`, which is a free-text account number held at the\nvendor. **The write input is still spelled `accountNumber`**; only the read\nsurface carries the new name."},"subAccount":{"type":"integer","format":"int64","description":"The sub-account (`subact`), or absent when the line has none.\n\n**`subaccountNumber` in v2**, one of four spellings this column carried.\n`subAccount` is the one `LedgerTransactionLine` already uses."},"invoiceLocation":{"type":"integer","format":"int32","description":"`invloc`.\n\n**The name is suspect.** The same Sage column is published as\n`inventoryLocation` on the inventory models and as `sourceLocation`\nelsewhere, so `invoiceLocation` may be a mislabel. Confirm against an\ninstall before depending on it."},"serialNumber":{"type":"string"},"subcontractRef":{"type":"string","description":"`linref`. A free-text reference to the subcontract line, not a record number."},"relatedInvoice":{"type":"integer","format":"int64","description":"`invrec`, the record number of a related invoice, or `0` when there is none.\n\nNot typed `RecordNumber` (`@minValue(1)`) for exactly that reason."},"userDefined1":{"type":"string"}},"description":"`the underlying record`, a payable invoice line, as read.\n\n**Absent from the published v2 spec entirely**, both the array and this\nschema, so a v2 reader had no way to discover the field names even after\nguessing the collection existed.\n\nAs with the parent, only `id` and `recordNumber` are always present."},"PayablePayment":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"description":{"type":"string","description":"`dscrpt`."},"checkNumber":{"type":"string","description":"`chknum`, the check number.\n\n**A string, not an integer.** The column holds what was written on the\ninstrument, including EFT and wire references, so it neither sorts nor\nincrements numerically."},"checkDate":{"type":"string","format":"date","description":"`chkdte`, the date on the check.\n\nA date, not an instant. With no change stamp on this table it is also the\nonly column that can bound a sync window, and it is a business date chosen by\nwhoever entered the payment rather than a row stamp."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, the payment."},"discountTaken":{"$ref":"#/components/schemas/Decimal","description":"`dsctkn`, the early-payment discount taken against the vendor.\n\nThe pairing with the invoice is exact: ``dsctkn`` is the discount the\ninvoice expects and ``dsctkn`` is the discount the check actually took."},"appliedCredit":{"$ref":"#/components/schemas/Decimal","description":"`aplcrd`, credit applied to the payment rather than paid in cash."},"period":{"type":"integer","format":"int16","description":"The period the payment posted into (`actper`), with `year`.\n\nThe same `actper`/`postyr` pair `PayableInvoice` carries. There is no status\ncolumn on this table, so unlike an invoice, where status decides whether a\njournal exists at all (see `ApInvoiceStatus`), a payment is either present\nwith a `ledgerRecord` or it is not."},"year":{"type":"integer","format":"int16","description":"`postyr`, the posting year that goes with `period`."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this payment posted, or `0` when\nno journal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason, and the only join\nthis entity offers; see the model note."}},"description":"`the underlying record`, a payable payment: a check to a vendor as Sage records it.\n\n**Column for column the same table as `the underlying record`**, down to the field order;\n`the underlying record` is a third copy, modelled as `ServicePayment`. All three are separate\nentities rather than one shared `Payment` because they are separate tables with\nseparate record-number sequences, and a `recordNumber` that means one row in AP\nand a different row in AR cannot share a resource.\n\n**The payee is not on this record.** `the underlying record` has no `vndnum` (no vendor, no\ninvoice, no job) and no child collection. `ledgerRecord` is the only join it\npublishes, so \"which vendor did we pay, and against which invoices\" is a\nquestion for `the underlying record`/`the underlying record`, not for this entity. Worth knowing before\nconcluding the API cannot answer it at all.\n\n**Neither `lastUpdated` nor `createdSince` is supported.** The table has\nneither `edtdte` nor `entdte`, so there is no change stamp to filter on. Both\nfilters answer 501 naming the table rather than silently returning everything:\na `lastUpdated` that is quietly a no-op would leave a caller believing it had\nreceived an incremental page.\n\nOnly `id` and `recordNumber` are required. Every other field is optional\nbecause a v3 read may restrict the response with `select`, so an absent field\nmeans \"not asked for\", not \"not set\"."},"Paygroup":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\nThe target of ``paygrp``, ``paygrp`` and ``paygrp``, all\npublished as `paygroup`. See `Employee.paygroup` for why that field keeps\nthe unsuffixed v2 name."},"groupName":{"type":"string","description":"`grpnme`, the paygroup's name."},"classification":{"type":"string","description":"`wrkcls`, the work classification: free text, and a **string**, not a\nforeign key into a classification table.\n\nThis is the trade classification certified-payroll and union reporting are\nbuilt on (Davis-Bacon and equivalents). OPDC neither validates nor resolves\nit."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a paygroup (Sage 100 Contractor **5-3-4**), as read.\n\nA paygroup bundles a work classification with the set of payroll calculations\nthat apply to it, so an employee assigned to it inherits union or\ncertified-payroll rates and benefits rather than carrying them individually.\nIt is one of the setup tables that must already exist in Sage, or a payroll\npost will fail validation.\n\n**The bundle itself is the `Benefit` entity, not a child collection here.**\n`the underlying record` rows carry `paygrp` and hold the per-calculation deduction rate, but\n`the underlying record` registers no child relation, so this entity takes no `children`\nargument. Read `Benefit` filtered on `paygroup` instead.\n\nFour columns and a UUID is the whole of what this entity publishes; Sage's own\npaygroup screen carries considerably more. OPDC never writes the table.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"PayrollCalculation":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\n**This is the \"calculation number\" the whole payroll contract is keyed on.**\n`TimecardCalculation.calculationNumber` (``clcnum``),\n`EmployeeEarnings.calculationNumber` (``clcnum``) and\n`Benefit.calculationNumber` (``dednum``) all point here, as does the\nwrite contract's `PayrollDeductionInput.calculation`. A deduction posted\nwithout it would credit the GL but write no `the underlying record` row, so a batch\ncontaining one is rejected rather than partially posted.\n\nIt is a foreign key and **not an enum**, however small the set of values is\non a given install."},"calcName":{"type":"string","description":"`clcnme`, the calculation's name: e.g. `Federal WH`, `Social Security`, `Dental`. Free text, set per install."},"calcType":{"type":"integer","format":"int16","description":"`clctyp`, the calculation type.\n\n**Deliberately not modelled as an enum.** This is the code that separates\nemployee-side withholding from employer-side burden, and it very likely\ncarries more distinctions than that, but the full domain is not documented\nanywhere this API can verify, and naming members on a guess would promote\nthat guess to a contract.\n\nThe distinction matters in practice: a payroll post writes the\n**employee-side journal only**, so employer FICA/Medicare match, FUTA, SUI\nand workers' comp are calculations that exist in this table and produce no\nOPDC journal at all."},"defaultRate":{"$ref":"#/components/schemas/Decimal","description":"`dftrte`, the default rate Sage applies when the calculation is added to an\nemployee. `EmployeeEarnings.deductionRate` is the per-employee override of\nit."},"defaultMax":{"$ref":"#/components/schemas/Decimal","description":"`dftmax`, the default annual maximum: the wage or amount cap. `EmployeeEarnings.deductionMax` overrides it per employee."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a payroll calculation (Sage 100 Contractor **5-3-1**), as read.\n\nOne row per tax or deduction: Federal WH, Social Security, Medicare, dental,\ngarnishments, employer burden. Sage's own label for the table is \"Payroll\nCalculations\"; `the underlying record`'s six characters spell \"payroll deduction\", which is\nnarrower than what it holds.\n\n**This table is where payroll's GL accounts live, and none of them are\npublished.** The debit (expense) accounts `dbtact`/`dbtshp`/`dbtovh`/`dbtadm`\nand the credit (liability) accounts `crdact`/`crdsub` are the accounts that\ndrive the payroll journal in Sage, and this entity does not return any of\nthem, so a caller **cannot** ask this API which account a deduction credits.\nThe batch contract requires the integration to send `liabilityAccount` per\ndeduction instead, which is the same number it had to learn from Sage by some\nother route.\n\n`the underlying record` registers no child relation, so no `children` argument. OPDC never\nwrites this table.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"PayrollCheckInput":{"type":"object","required":["employeeNumber","earnings","grossPay","netPay"],"properties":{"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"``recnum``. Required; the script fails the batch without it."},"employeeCode":{"type":"string","description":"The provider's employee code, echoed back for correlating messages only. Not written to any Sage column."},"checkNumber":{"type":"string","description":"Used as the ledger transaction number (``trnnum``). Not validated for\nuniqueness, the same caveat as `LedgerTransactionInput.transactionNumber`.\nAbsent posts with an empty transaction number rather than being rejected."},"stateCode":{"type":"string","description":"``taxste``. Free text."},"payType":{"type":"integer","format":"int16","description":"``paytyp``, the check's own type code (e.g. `1` = computer check).\n**Not the earning pay-type enum.** See `PayEarningType`'s doc comment.\nDefaults to `1`, matching the script.","default":1},"status":{"type":"integer","format":"int16","description":"``status``. Defaults to `1` (\"posted\"), matching the script.\nIndependent of the ledger transaction's own status: the journal this\nscript posts is always status `1` (Open) regardless of this value:\nthere is no caller control over GL propagation for payroll the way\n`LedgerTransactionInput.status` gives ledger callers one.","default":1},"earnings":{"type":"array","items":{"$ref":"#/components/schemas/PayrollEarningInput"},"minItems":1,"description":"At least one. Every earning's `amount` must sum to `grossPay`."},"deductions":{"type":"array","items":{"$ref":"#/components/schemas/PayrollDeductionInput"},"description":"Optional: a check may have no deductions. `grossPay` must then equal `netPay`."},"grossPay":{"$ref":"#/components/schemas/Decimal","description":"Must equal the sum of `earnings[].amount`."},"deductionTotal":{"$ref":"#/components/schemas/Decimal","description":"Must equal the sum of `deductions[].amount`. Optional: when omitted the\nserver derives it as `grossPay - netPay`, matching the script's\nfallback."},"netPay":{"$ref":"#/components/schemas/Decimal","description":"Must equal `grossPay - deductionTotal`. Credited to `PayrollInput.payrollCashAccount`."}},"description":"One employee's paycheck within the batch. Produces one `the underlying record` row (plus\nits `the underlying record`/`the underlying record`/`the underlying record` children) and one balanced ledger\ntransaction."},"PayrollCheckResult":{"type":"object","required":["id","recordNumber","employeeNumber","checkNumber","payType","status","grossPay","deductionTotal","netPay","ledgerTransaction"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber"},"employeeCode":{"type":"string"},"checkNumber":{"type":"string"},"payType":{"type":"integer","format":"int16"},"status":{"type":"integer","format":"int16"},"grossPay":{"$ref":"#/components/schemas/Decimal"},"deductionTotal":{"$ref":"#/components/schemas/Decimal"},"netPay":{"$ref":"#/components/schemas/Decimal"},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Always present: every check in a batch posts its own ledger transaction."}},"description":"One posted paycheck, as written."},"PayrollDeductionInput":{"type":"object","required":["calculation","amount","liabilityAccount"],"properties":{"calculation":{"$ref":"#/components/schemas/RecordNumber","description":"``recnum``, the deduction-calculation code. **Required, despite being\noptional in v2**: `the underlying record` is keyed by this value and the deduction is\ncredited to the general ledger regardless of whether it is supplied, so an\nabsent value would desync the payroll subledger from the ledger. A deduction\nwithout it causes the whole batch to be rejected."},"code":{"type":"string","description":"Free text (e.g. `FWT`, `SSC`, `MED`). Used in the GL line description, truncated to 30 characters."},"amount":{"$ref":"#/components/schemas/Decimal","description":"Employee-side amount. Credited to `liabilityAccount` only when non-zero."},"liabilityAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"``crdact``. Required when `amount` is non-zero."},"liabilitySubAccount":{"type":"integer","format":"int64","description":"``crdsub``."}},"description":"One tax or deduction on a check. Written as a `the underlying record` row and credited to\nits liability account in the GL journal."},"PayrollEarningInput":{"type":"object","required":["amount"],"properties":{"code":{"type":"string","description":"Free text (e.g. `REG`, `OT`, `PTO`). Truncated to 50 characters into ``dscrpt``."},"payType":{"$ref":"#/components/schemas/PayEarningType","description":"Defaults to Regular when absent, matching the script's fallback.","default":1},"hours":{"$ref":"#/components/schemas/Decimal","description":"Hours worked. For `Piece` this is the piece count (``pieces`` /\n``pieces``), not hours. Ignored for `PerDiem` and `Miscellaneous`,\nwhich are lump sums with no quantity.","default":"0.00"},"rate":{"$ref":"#/components/schemas/Decimal","description":"Pay rate. For `Piece` this is the per-piece rate (``pcerte`` /\n``pcerte``). Cross-checked against ``payrt1``/`payrt2`/`payrt3`\nwithin `PayrollInput.rateTolerance` for `Regular`/`Overtime`/`Premium`\nonly, because those are the only three rate columns the employee master has.","default":"0.00"},"amount":{"$ref":"#/components/schemas/Decimal","description":"The dollar amount of this earning. Every earning's `amount` must sum to\nthe check's `grossPay`; the server rejects the whole batch by employee\notherwise."},"account":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"DR wage/labor expense account for this earning. Required unless\n`PayrollInput.wageExpenseAccount` is supplied as a batch-wide fallback;\nan earning with neither is rejected."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"Optional job link, stored on the `the underlying record` line (`jobnum`) for job-cost\nreporting. **Unlike the AP-invoice and service-inventory scripts, this\nscript never writes an `the underlying record` job-cost row**: there is no\n`jobCosts` input here, and the write result's `jobCosts` is always\nempty. This is metadata on the timecard line only."},"costCode":{"type":"integer","format":"int64","description":"Job cost code, stored on ``cstcde``. A plain install-specific\nnumber here, unlike `JobCostInput.costCode` elsewhere in this spec\n(`Decimal`): the two are different columns on different tables and\nthis one is never validated against a cost-code lookup by the script."},"department":{"type":"integer","format":"int64","description":"`the underlying record` / `the underlying record` distribution department. Earnings are grouped by (account, department) into one `the underlying record` leg per group."},"phase":{"type":"integer","format":"int64","description":"Optional job phase, stored on ``phsnum``. No default: absent stays null, unlike `JobCostInput.phaseNumber`'s `0` default."}},"description":"One earning line within a check. Written as a `the underlying record` timecard line and\nrolled into the matching `the underlying record` hours/pay bucket by `payType`."},"PayrollInput":{"type":"object","required":["checkDate","periodStart","periodEnd","payrollCashAccount","checks"],"properties":{"userName":{"type":"string","description":"Falls back to the SQL user when absent, matching every other write endpoint."},"provider":{"type":"string","description":"Free text (e.g. `\"paycom\"`, `\"adp\"`), for audit/logging only. Not validated and not written to any Sage column."},"checkDate":{"type":"string","format":"date","description":"The GL transaction date for every check in the batch; drives the posting period, resolved once for the whole run."},"periodStart":{"type":"string","format":"date","description":"Pay period start, written to ``strprd``."},"periodEnd":{"type":"string","format":"date","description":"Pay period end, written to ``payprd`` and ``dtewrk``."},"payrollCashAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"CR net pay for every check in the batch. Required; the script fails the batch without it."},"wageExpenseAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"Fallback DR account for an earning that omits its own `account`."},"sourceNumber":{"type":"integer","format":"int32","description":"GL source override, stamped on ``srcnum``.\nDefaults to `16` (Payroll, Sage 5-2-6 \"Post Payroll\") when absent.\nInstall-specific above 33, same caveat as\n`LedgerTransactionInput.sourceNumber`: validate an override against the\ntransaction-source lookup rather than assuming."},"rateTolerance":{"$ref":"#/components/schemas/Decimal","description":"Allowed rate drift, per earning, against the matching ``payrt1``/\n`payrt2`/`payrt3` column. Defaults to `0.01`, matching the script.","default":"0.01"},"checks":{"type":"array","items":{"$ref":"#/components/schemas/PayrollCheckInput"},"minItems":1,"description":"At least one check. The whole batch is rejected together if any check fails validation."}},"description":"A provider-agnostic payroll batch: `POST /payroll` posts one `the underlying record` (plus\ntimecard, calculation and GL-distribution children) and one balanced\nemployee-side ledger transaction per check in `checks`."},"PayrollPosition":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). The target of ``paypst``, published as `Employee.positionNumber`."},"positionName":{"type":"string","description":"`pstnme`, the position's name. Free text, set per install, and **deliberately not an enum**."},"department":{"type":"integer","format":"int64","description":"`dptmnt`. A record number into the department table; `0` when unset.\n\nThe department wages for this position default to. Note that the department\nactually posted comes from the earning line, not from here. See the model\nnote."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a payroll position, as read.\n\nThe job-title lookup an employee is assigned to (Foreman, Estimator, Field\nLabor), carrying the department that title's wages default to. Four columns\nand a UUID is the whole of it.\n\n**This is a lookup, and it is the target of `Employee.positionNumber`**\n(``paypst``), which is the only column in the product that points at it.\nOPDC never writes the table, and no payroll posting reads it: a posted batch\nroutes wages by the earning's own `account` and `department` rather than by\nthe employee's position, so a wrong or missing position affects reporting, not\nthe journal.\n\n`the underlying record` registers no child relation, so no `children` argument.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"PayrollPosting":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only identifier this row has of its own;\n`recordNumber` belongs to the parent."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the parent check's record number, not this row's.** A foreign\nkey to ``recnum``, the `PayrollRecord` entity's `recordNumber`. With no\n`idRef` on this model, it is the only way to reach the check. See\n`TimecardLine.recordNumber` for why the name is kept."},"postingNumber":{"type":"integer","format":"int32","description":"`pstnum`, the leg's position within the check's distribution: `1`-based, in the order the earnings were grouped."},"department":{"type":"integer","format":"int64","description":"`dptmnt`. A record number into the department table; `0` when unset.\n\n**Half the grouping key.** Earnings are collapsed into legs by (account,\ndepartment), so two earnings on the same account in different departments are\ntwo rows here and two legs in the journal."},"debitAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`dbtact`, the GL account debited: the wage or labour expense account.\n\nComes from the earning's own `account`, falling back to the batch's\n`wageExpenseAccount`; an earning with neither fails the batch before anything\nis written, so unlike `PayrollRecord.ledgerRecord` this is never legitimately\n`0` on an OPDC-written row, hence `LedgerAccountNumber` rather than a bare\n`int64`."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, the leg's debit. Accumulated with exact decimal arithmetic rather\nthan floating point.\n\nThe sum of `amount` over a check's rows equals `PayrollRecord.grossAmount`,\nwhich is the same identity the GL post re-validates as `debits == credits`\nbefore writing the journal."}},"description":"`PSTPYR`, a payroll posting line: **the GL debit distribution for a\npaycheck**, as read.\n\nOne row per (debit account × department) on the check: the wage and labour\nexpense side of the payroll journal, grouped so that the distribution and the\njournal match leg for leg. Sage builds it at **5-2-7 Post Payroll**; OPDC\nbuilds it inline, from the earnings, in the same transaction as the journal.\n\n**This is the debit side only.** The two credit legs (tax and deduction\npayable per ``crdact``, and payroll cash for the net) are in the ledger\ntransaction, not here. Reading `PSTPYR` alone tells you what was expensed,\nnever what was paid or withheld; `PayrollRecord.ledgerRecord` is the link to\nthe journal that holds both sides.\n\n**This model has no `idRef`, and it is the odd one out for it.** Every other\npayroll detail entity publishes the parent check's UUID; this one does not,\nbecause the column is not in its projection. Join on `recordNumber` instead,\nwhich is present and carries the same relation.\n\nLike `TimecardLine` and `TimecardCalculation`, a detail table routed as a\ntop-level entity because `the underlying record` declares no child relation; see\n`TimecardLine`'s model note.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"PayrollRecord":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`): this check's number in Sage.\n\n**This is also the value `the underlying record`, `the underlying record` and `the underlying record` carry in their own\n`recnum` column**, so it is the join key for every detail read. The detail\nrows additionally carry this row's `id` in `_idref`, published as `idRef`,\nexcept on `PayrollPosting`, which does not publish that column."},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`empnum`, a foreign key to ``recnum``: the `Employee` entity's `recordNumber`."},"periodStart":{"type":"string","format":"date","description":"`strprd`, the pay-period start. A date, not an instant."},"periodEnd":{"type":"string","format":"date","description":"`payprd`, the pay-period **end date**.\n\n**``payprd`` is the same column name holding something else entirely**:\nthe employee's pay-period frequency code, an integer, published as\n`payPeriod`. Two tables, one column name, two types."},"checkNumber":{"type":"string","description":"`chknum`, the check number, and **a string, not an integer**. Providers commonly\nzero-pad their numbers (`\"000004256\"`) and parsing them as integers loses the\npadding.\n\nThis is the field to filter on to establish whether a payroll post committed\nwhen the response to it was lost."},"checkDate":{"type":"string","format":"date","description":"`chkdte`, the check date. Drives the GL period for the whole batch. A date, not an instant."},"payrollType":{"type":"integer","format":"int16","description":"`paytyp`, the **check's** type code (Sage's \"computer check\" type); the\nwrite contract defaults it to `1`.\n\n**Not the earning pay type.** ``paytyp`` is the same column name on the\ndetail table holding the 1..9 Regular/Overtime/…/Miscellaneous family, and\nthe two are published under different names: `payrollType` here, `payType`\nthere. Do not map one family onto the other."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`: `1-Open` (not yet computed), `2-Computed`,\n`3-Posted`, `4-Reversed`, `5-Void`, verified against Sage's own help.\n\nNote it does **not** share the AP `1-Open`/`2-Review`/`3-Dispute` shape:\nposition 2 onward is a different family entirely. It is typed as an integer\nrather than an enum because the shared enum this API would draw it from does\nnot exist yet.\n\n**What Sage stores here may not describe what OPDC did.** A payroll post\nstamps whatever status the caller sent, defaulting to `0` (a member of no\nfamily), even though posting the journal and writing back `ledgerRecord` is\nSage's `3-Posted` action. A row OPDC posted should read `3` and often will\nnot."},"quarter":{"type":"integer","format":"int16","description":"`qtrnum`, the calendar quarter this check falls in. Sage maintains it; OPDC never writes it."},"grossAmount":{"$ref":"#/components/schemas/Decimal","description":"`grspay`, gross pay. The check register's `grosspay`, and the amount a\nposted batch is rejected on if its earnings do not foot to it."},"netAmount":{"$ref":"#/components/schemas/Decimal","description":"`netpay`, net pay. `grossAmount − Σ(taxes + deductions)`, enforced before any\nrow is written, and the amount credited to the batch's `payrollCashAccount`\nin the journal."},"regularPay":{"$ref":"#/components/schemas/Decimal","description":"`regpay`. Earnings of pay type `1-Regular`."},"overtimePay":{"$ref":"#/components/schemas/Decimal","description":"`ovtpay`. Pay type `2-Overtime`."},"premiumPay":{"$ref":"#/components/schemas/Decimal","description":"`prmpay`. Pay type `3-Premium` (double time)."},"sickPay":{"$ref":"#/components/schemas/Decimal","description":"`sckpay`. Pay type `4-Sick`."},"vacationPay":{"$ref":"#/components/schemas/Decimal","description":"`vacpay`. Pay type `5-Vacation`."},"holidayPay":{"$ref":"#/components/schemas/Decimal","description":"`holpay`. Pay type `6-Holiday`."},"additionsTotal":{"$ref":"#/components/schemas/Decimal","description":"`addttl`, the additions total. Never written by a payroll post, which posts deductions only."},"deductionsTotal":{"$ref":"#/components/schemas/Decimal","description":"`dedttl`, the deductions total. `TimecardCalculation` is the per-calculation\ndetail behind this one number."},"perDiem":{"$ref":"#/components/schemas/Decimal","description":"`perdim`. Pay type `8-Per Diem`: a lump sum carrying no hours."},"miscPay":{"$ref":"#/components/schemas/Decimal","description":"`mscpay`. Pay type `9-Miscellaneous`: lump sums and bonuses; Sage has no Bonus type."},"salaryAmount":{"$ref":"#/components/schemas/Decimal","description":"`salary`, the salary amount paid on this check. `Employee.salaryAmount` is the same column on the master."},"totalHours":{"$ref":"#/components/schemas/Decimal","description":"`ttlhrs`, total hours. Equals the sum of the six hour buckets exactly,\nbecause pay types 7, 8 and 9 carry no hours at all, **and those six buckets\nare not published by this model**; see the model note."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the link to the GL: ``recnum``, the `LedgerTransaction`\nentity's `recordNumber`. Stamped once the journal has posted.\n\n**`0` means the check has not been posted to the general ledger**, which is\nwhy this is typed `int64` and not `RecordNumber`: that scalar requires a\nvalue of at least `1`, and here `0` is an expected, meaningful value rather\nthan a missing one."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. A date, not an instant."},"userName":{"type":"string","description":"`usrnme`. The batch's `userName`, falling back to the SQL user."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a payroll record: **one row per paycheck** (Sage 100 Contractor\n**5-2-2**), as read.\n\nThe header of the payroll document. Its three detail tables are\n`TimecardLine` (`the underlying record`, one row per earning), `TimecardCalculation`\n(`the underlying record`, one row per tax or deduction) and `PayrollPosting` (`the underlying record`, the GL\ndebit distribution); `ledgerRecord` is the link out to the journal that posted\nit.\n\n**The three detail tables are separate top-level entities, not `children` of\nthis one.** `the underlying record` declares no child relation, so there is no `children`\nargument here and no nested read. A caller assembles a check by querying each\ndetail entity filtered on **its** `recordNumber`, which on those tables is\nthis row's record number, not the detail row's own.\n\n**Sage columns this model does not read**, all of them present in the database\non a check OPDC posted: the six hour buckets\n`reghrs`/`ovthrs`/`prmhrs`/`sckhrs`/`vachrs`/`holhrs`, the per-check rates\n`payrt1`/`payrt2`/`payrt3`, the tax state `taxste`, the piece-work pair\n`pieces`/`pcerte`, direct deposit `dirdep`/`ddpbch`, and the year-to-date\naccumulators `ytdgrs`/`ytdnet`. None of them are in this entity's projection,\nso no v3 query can return them. Worth knowing before concluding the API\ncannot answer a question about hours by pay type.\n\nNo `lstupd` column, so no `lastUpdated` filter and no incremental read.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"PayrollResult":{"type":"object","required":["checkDate","checkCount","checks"],"properties":{"provider":{"type":"string"},"checkDate":{"type":"string","format":"date"},"checkCount":{"type":"integer","format":"int32","description":"Matches `checks[]`'s length; carried over from the script's own `checkCount`."},"checks":{"type":"array","items":{"$ref":"#/components/schemas/PayrollCheckResult"}}},"description":"A posted payroll batch, as written.\n\nNote what is **not** here, mirroring `LedgerTransaction`'s callout about its\nown absent `jobCosts`: individual earnings and deductions are not echoed\nback as their own result objects. The script returns only the `the underlying record`\nheader plus the linked ledger transaction per check; `the underlying record`/`the underlying record`\nrows are written but never read back into the response."},"PayrollWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun","period","year"],"properties":{"document":{"$ref":"#/components/schemas/PayrollResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into."}},"description":"The body of a successful `POST /payroll`.\n\n`jobCosts` and `inventory` are always empty here: an earning may carry a job\nnumber, cost code, phase and department, but those land on the timecard line\nfor reporting only and never produce a job-cost row, and payroll moves no\ninventory. `period` and `year` are the run's, resolved once from `checkDate`\nrather than per check."},"PeriodStatus":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only identifier this table has; see the model note."},"year":{"type":"integer","format":"int16","description":"`postyr`, Sage's \"Year\", `Numeric(4)`. Half of the natural key."},"period":{"type":"integer","format":"int16","description":"`actprd`, Sage's \"Period\", `Numeric(2)`. The other half of the natural key.\n\n`actprd` here and on `the underlying record`, `the underlying record`, `the underlying record`, `the underlying record` and `the underlying record`, but\n**`actper`** on `the underlying record`, `the underlying record` and `the underlying record`."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`.\n\n**Deliberately not an enum, and deliberately undescribed.** Sage publishes no\ndocumentation for this column and the observed values contradict the obvious\nopen/closed reading, so naming members here would be a plausible guess\npromoted to a contract. See the model note."}},"description":"`the underlying record`, the period-status table (1-6).\n\n## This entity has no `recordNumber`\n\n**`the underlying record` has no `recnum` column**, so no `recordNumber` is declared rather\nthan one being invented. The rule that `id` and `recordNumber` are always\npresent does not hold here; `id` is the whole identity, and the natural key is\nthe `year` + `period` pair. `LedgerSetup` is the only other entity in this\nposition, and for the same reason.\n\n## `status` does not mean open/closed\n\nThe obvious reading, that the flag says whether a period is open for posting,\n**is wrong**, and was checked against real data: periods known from the Sage UI\nto be open for posting carry both values, on either side of a fiscal-year\nboundary. What the data supports is narrower: `the underlying record` distinguishes prior\nfiscal years from the current one, and the postable range crosses that\nboundary.\n\n**So do not build posting validation on this table.** Nothing in this API\nconsults it when posting either; it is published so a caller can see what Sage\nstores, not because anything acts on it. The postable range is derived instead\nfrom `LedgerSetup`'s `fiscalYearEndDate` and `currentPeriod`: the current\nfiscal year's twelve periods, plus the prior year's period 12."},"PingResult":{"type":"object","required":["status","latencyMs"],"properties":{"status":{"type":"string","description":"`ok` when the on-premise agent answered."},"latencyMs":{"type":"integer","format":"int64","description":"Round-trip time to the on-premise agent and back, in milliseconds."},"response":{"description":"The agent's raw reply. Diagnostic; its shape is not contractual."},"schemaCompat":{"$ref":"#/components/schemas/SchemaCompatibility","description":"The Sage schema compatibility state.\n\nThis is the only way to read that state on a machine you have no shell\naccess to, which is what makes ping worth calling rather than just timing a\nrequest."}},"description":"The result of a ping."},"PreLien":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`). The only unique key on this table."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the job number**, a foreign key to ``recnum``, not a\nper-row record number. Not unique. See the model note."},"ownerName":{"type":"string","description":"`ownnme`, the property owner's name."},"ownerAddress1":{"type":"string","description":"`ownad1`."},"ownerAddress2":{"type":"string","description":"`ownad2`."},"ownerCity":{"type":"string","description":"`owncty`."},"ownerState":{"type":"string","description":"`ownste`."},"ownerZip":{"type":"string","description":"`ownzip`."},"lenderName":{"type":"string","description":"`lndnme`, the lender's name.\n\nThe lender's address columns (`lndad1`, `lndad2`, `lndcty`, `lndste`,\n`lndzip`) exist in Sage and are not returned. See the model note."},"contractorName":{"type":"string","description":"`connme`, the general contractor's name. Its address columns\n(`conad1`-`conzip`) are likewise not returned.\n\nDistinct from `Job.contractNumber` (`connum`), which is free text and shares\nonly the `con` prefix."},"description":{"type":"string","description":"`dscrpt`, a Sage memo field: the last of the table's 31 columns, not a short label."}},"description":"`the underlying record`, a pre-lien notice (3-5 Jobs).\n\nA pre-lien is the preliminary notice a contractor serves on the parties with an\ninterest in a job (owner, lender, general contractor, subcontractor, and the\nparty the notice is sent to) to preserve mechanic's lien rights. Sage stores\nall five parties as name-and-address blocks on one row.\n\n**`recnum` on this table is the JOB number, not this row's identity.** Sage\nlabels it \"Job#\" and declares it a foreign key to ``recnum``.\n`recordNumber` is not unique; `id` is. Same shape as `JobPhase` and\n`JobPaygroup`.\n\n**This API returns 10 of Sage's 31 columns on this table, and the gap is not\ncosmetic.** The owner block is complete (`ownnme` through `ownzip`), but only\nthe *name* is available for the lender and the general contractor, and nothing\nat all for the subcontractor or the contact. A caller serving notices cannot\nget an address for four of the five parties from this endpoint."},"Problem":{"type":"object","required":["type","title","status"],"properties":{"type":{"type":"string","format":"uri","description":"A URI identifying the problem type. Stable and machine-matchable.","examples":["https://twinn.engineering/errors/period-closed"]},"title":{"type":"string","description":"Short, stable, human-readable summary. Does not vary by occurrence.","examples":["Posting period is closed"]},"status":{"type":"integer","format":"int32","description":"The HTTP status code, repeated for clients that lose it."},"detail":{"type":"string","description":"Human-readable detail for *this* occurrence. May name specifics.","examples":["transactionDate 2025-11-04 resolves to period 11/2025; open periods are 12/2025 and 1-12/2026"]},"instance":{"type":"string","description":"The request path."},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ProblemErrorItem"},"description":"Present when the problem is attributable to specific input fields."},"correlationId":{"type":"string","description":"The `X-Correlation-Id` of the request, so you can quote one token when\nraising a ticket. Server errors are redacted before they reach you; this is\nthe handle to the unredacted record."}},"description":"RFC 9457 problem detail. **The only error shape in v3, on every status code.**\n\nReplaces all of: `200` + `responseType: \"ERROR\"` on reads, `500` + the same\nenvelope on writes, and the `text/plain` bodies on 400/401/409/423/503/504.\n`responseType` does not exist in v3."},"ProblemErrorItem":{"type":"object","required":["field","code"],"properties":{"field":{"type":"string","description":"JSON path of the offending field, for example `lines[2].debitAmount`."},"code":{"$ref":"#/components/schemas/ErrorCode","description":"The stable machine code for **this field's** failure, which is not\nnecessarily the code on the enclosing `Problem`: a 400 whose `type` is\n`request-invalid` can carry a `FIELD_REQUIRED` and a `FIELD_INVALID` item\ntogether."}},"description":"One error field, so a caller can bind a failure to the input that caused it."},"ProgressBilling":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, the job phase.\n\nAn unconstrained integer rather than a record number, because Sage writes `0`\nfor \"no phase\". This matches `Subcontract`, `BudgetLine` and\n`CostToCompleteLine`."},"applicationNum":{"type":"integer","format":"int32","description":"`appnum`, the application number: which billing in the series this is.\n\nSage's own G702 field, and the number the architect and owner refer to. Not a\nrecord number and not unique across jobs; `recordNumber` identifies the row."},"billingDate":{"type":"string","format":"date","description":"The date of this application (`period`; yes, the column is named `period` and\nholds a `Date`; see the model note).\n\nA date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage `Date`\ncolumn."},"primaryRate":{"$ref":"#/components/schemas/Decimal","description":"`retain`, the form's retainage rate.\n\n**The name is suspect and is kept only because nothing verifies the\nalternative.** The surrounding columns say the *rate* half is right and the\n*primary* half is the question: `retentionTotal` below is this form's\nretainage in money and ``retentionRate`` is the per-line rate, which\nleaves a header default rate as the only role left for `retain`. Confirm\nagainst an install before depending on it."},"description":{"type":"string","description":"`dscrpt`."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``. Install-specific rather than a\nfixed set; query the tax-district lookup."},"scheduledTotal":{"$ref":"#/components/schemas/Decimal","description":"`schttl`, the scheduled value for the whole form: the sum of the lines' `scheduledAmount`."},"changesTotal":{"$ref":"#/components/schemas/Decimal","description":"`chgttl`, change orders in total. `Subcontract` publishes its own `chgttl` under this same name."},"contractTotal":{"$ref":"#/components/schemas/Decimal","description":"`conttl`, the contract after change orders.\n\nNote this is `conttl`, a column unique to `the underlying record`, not ``ctcttl``\n(`newContractTotal`) and not `cntrct` (`contractAmount` on `the underlying record` and\n`the underlying record`). Three tables express \"the contract after changes\" in three columns;\nthis is the progress billing's."},"previousTotal":{"$ref":"#/components/schemas/Decimal","description":"`prvttl`, billed on prior applications."},"currentTotal":{"$ref":"#/components/schemas/Decimal","description":"`curttl`, billed on this application."},"completedTotal":{"$ref":"#/components/schemas/Decimal","description":"`cmpttl`, completed and stored to date."},"percentComplete":{"$ref":"#/components/schemas/Decimal","description":"`pctcmp`, `completedTotal` against `contractTotal`. A JSON string like every other decimal, not a number."},"balanceTotal":{"$ref":"#/components/schemas/Decimal","description":"`balttl`, the balance to finish. `Subcontract` publishes its own `balttl` under this same name."},"retentionTotal":{"$ref":"#/components/schemas/Decimal","description":"`retttl`, retainage withheld across the form, in money. See `primaryRate`."},"invoiceTotal":{"$ref":"#/components/schemas/Decimal","description":"`invttl`, the amount actually invoiced from this application."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`.\n\n**Deliberately published as an integer, with no member list**, because the\nvalue set for this column is not verified. Do **not** assume the\n`1-Open` / `2-Review` / `3-Dispute` sequence that several other Sage families\nbegin with; families that share those first three diverge from position 4\nonward."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this billing posted, or `0` when\nno journal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason, and spelled\n`ledgerRecord` like the other fourteen models carrying `lgrrec`."},"hotList":{"type":"integer","format":"int16","description":"`hotlst`, Sage `Numeric(1)`, dictionary default `1`. Sage's \"Hot List\" flag.\n\nPublished as the integer it is rather than guessed at as a boolean or an\nenum, because the value set is not verified. `the underlying record` and `the underlying record` carry the\nsame column under the same name."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ProgressBillingLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`.\n\nChildren ignore `select` and are unpaginated and unfilterable."}},"description":"`the underlying record`, a progress billing (an AIA application for payment), as read.\n\nThe header of the G702/G703 pair: one application against one job and phase,\nwith the totals its lines roll up to. `applicationNum` is what makes a series of\nthem a sequence.\n\n**`billingDate` comes from a column named `period`, and `period` on this entity\ndoes not exist.** ``period`` is a `Date`, published as `billingDate`; the\naccounting-period pair every invoice and payment in this group carries\n(`actper` + `postyr`, published as `period` + `year`) is **not on this table at\nall**. A caller filtering `period` here expecting an integer fiscal period would\nbe comparing against a date, which is why the date does not take that name.\n\n**Neither `lastUpdated` nor `createdSince` is supported.** `the underlying record` has neither\n`edtdte` nor `entdte`, so there is no change stamp to filter on and both are a\n501 naming the table rather than a filter that quietly matches everything.\n\nOnly `id` and `recordNumber` are required. Every other field is optional because\na v3 read may restrict the response with `select`."},"ProgressBillingLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `ProgressBilling`, and the column the child\nfetch joins on. Returned even when nested under the parent it names, because\nthe child `SELECT` ignores `select`."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"divisionNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`divnum`, a foreign key to ``recnum`` (Cost Divisions). Query the\ncost-division lookup."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"description":{"type":"string","description":"`dscrpt`, the schedule-of-values description of the work."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``.\n\nA vendor on a *billing* line: the subcontractor whose work this item covers,\ncarried so the billing can be reconciled against what is owed out."},"scheduledAmount":{"$ref":"#/components/schemas/Decimal","description":"`schamt`, the scheduled value: the original contract amount for this item."},"changeAmount":{"$ref":"#/components/schemas/Decimal","description":"`chgamt`, the change-order movement on this item."},"newContract":{"$ref":"#/components/schemas/Decimal","description":"`newcon`, the contract for this item after change orders: `scheduledAmount` + `changeAmount`."},"previousBilled":{"$ref":"#/components/schemas/Decimal","description":"`prvbll`, billed on prior applications."},"currentBilled":{"$ref":"#/components/schemas/Decimal","description":"`curbll`, billed on this application."},"retentionRate":{"$ref":"#/components/schemas/Decimal","description":"`retrte`, the retainage rate withheld on this line. A rate, not an amount;\n`retentionAmount` is the amount."},"totalCompleted":{"$ref":"#/components/schemas/Decimal","description":"`ttlcmp`, completed and stored to date: `previousBilled` + `currentBilled`."},"percentComplete":{"$ref":"#/components/schemas/Decimal","description":"`pctcmp`, `totalCompleted` against `newContract`."},"balanceToFinish":{"$ref":"#/components/schemas/Decimal","description":"`balfin`, the balance to finish: `newContract` less `totalCompleted`."},"retentionAmount":{"$ref":"#/components/schemas/Decimal","description":"`retamt`, the retainage withheld on this line, in money."},"userDefined1":{"type":"string","description":"`usrdf1`."}},"description":"`the underlying record`, a progress-billing line, as read.\n\nOne row per schedule-of-values item. The money columns are the AIA G703\ncontinuation sheet, in its own order: the scheduled value, the change orders\nagainst it, the resulting contract, what was billed before and this period, the\ntotal completed, the percent, the balance to finish and the retainage.\n\nReading it as an AIA form is the fastest way to check a caller's arithmetic:\n`newContract` = `scheduledAmount` + `changeAmount`, `totalCompleted` =\n`previousBilled` + `currentBilled`, `percentComplete` = `totalCompleted` over\n`newContract`, and `balanceToFinish` = `newContract` − `totalCompleted`.\n**Nothing in this API validates any of that** (document totals are accepted as\nstored and never cross-checked), so a line that fails these identities is\npossible and will be returned.\n\n**This line has no `ntetxt`**, alone among the five line tables in this group.\nNot an omission in the read model: Sage declares no note column on it.\n\nEvery decimal is a JSON **string** at native scale, including `percentComplete`\nand `retentionRate`."},"Proposal":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`), which for this entity is the job number."},"lockEdit":{"type":"integer","format":"int16","description":"Sage's edit lock (`lckedt`, a one-digit flag).\n\n**Published as an integer with no member list**, because the value set for\nthis column is not verified. Deliberately not re-encoded as a boolean either,\nsince that would change the type of a value whose full domain nobody has\nconfirmed."},"hotList":{"type":"integer","format":"int16","description":"`hotlst`, Sage `Numeric(1)`, dictionary default `1`. Sage's \"Hot List\" flag.\n\nPublished as the integer it is, because the value set is not verified.\n`the underlying record` and `the underlying record` carry the same column under the same name."},"editDate":{"type":"string","format":"date","description":"When the proposal was last edited (`edtdte`).\n\nA date, not an instant: v2 returned `2026-01-15T00:00:00Z` for a Sage `Date`\ncolumn. This is the column `lastUpdated` filters on for this entity; see the\nmodel note."},"userName":{"type":"string","description":"The Sage user who last edited the proposal (`usrnme`)."},"proposalBasis":{"type":"integer","format":"int16","description":"`colshw`, which basis the proposal is presented on.\n\n**Published as an integer with no member list**, because the value set for\nthis column is not verified. The column mnemonic reads \"columns shown\", a\ndisplay setting for whether the five cost-type buckets on each line are broken\nout or rolled into `totalProposal`, and `proposalBasis` is an interpretation\nof that rather than Sage's own label. Resolve the members against Sage's help\nbefore branching on it."},"noteText":{"type":"string","description":"Record note (`ntetxt`)."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ProposalLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`.\n\nChildren ignore `select` and are unpaginated and unfilterable."}},"description":"`the underlying record`, a job's proposal, as read.\n\nOne per job. Sage keys `the underlying record` on `recnum` and there is no job column on the\ntable; its lines are keyed on phase and cost code, which exist only inside a\njob. That is the same arrangement as `the underlying record`, `the underlying record`, `the underlying record` and `the underlying record`,\nand this header is the same four columns those carry plus `hotlst` and\n`colshw`.\n\n**The route is `/proposals`**, an ordinary plural, unlike the sibling\nprojections whose routes are pluralised mechanically.\n\n**One of only seven entities where `lastUpdated` is honoured rather than\nanswered with a 501**: ``edtdte`` is a real edit stamp, alongside\n`the underlying record`, `the underlying record`, `the underlying record`, `the underlying record`, `the underlying record` and `the underlying record`.\n\n`lines` has always existed at runtime; nothing in the published v2 spec\ndescribed it.\n\nOnly `id` and `recordNumber` are required. Every other field is optional because\na v3 read may restrict the response with `select`, so an absent field means\n\"not asked for\", not \"not set\"."},"ProposalLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The parent proposal's record number (`recnum`). Repeated on every line; see\nthe note above."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `Proposal`, and the column the child fetch\njoins on. Returned even when nested under the parent it names, because the\nchild `SELECT` ignores `select`."},"phaseNumber":{"type":"integer","format":"int64","description":"The job phase (`phsnum`), a foreign key to the job-phase lookup."},"lineNumber":{"type":"integer","format":"int32","description":"Position within the phase (`linnum`)."},"bidItem":{"type":"integer","format":"int64","description":"`itmnum`, the bid item this line rolls up to.\n\nA foreign key into Sage's bid items, and install-specific: not an enum, query\nthe lookup."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"description":{"type":"string","description":"`dscrpt`, the description as it appears to the client."},"materialProposal":{"$ref":"#/components/schemas/Decimal","description":"`matprp`, the material bucket. Cost type `1`."},"laborProposal":{"$ref":"#/components/schemas/Decimal","description":"`labprp`, the labor bucket. Cost type `2`."},"equipmentProposal":{"$ref":"#/components/schemas/Decimal","description":"`eqpprp`, the equipment bucket. Cost type `3`."},"subProposal":{"$ref":"#/components/schemas/Decimal","description":"`subprp`, the subcontract bucket. Cost type `4`; Sage labels it \"Subcontract\", as on `BudgetLine.subBudget`."},"otherProposal":{"$ref":"#/components/schemas/Decimal","description":"`othprp`, the other bucket. Cost type `5`, and where an install's own cost types land if they land anywhere."},"totalProposal":{"$ref":"#/components/schemas/Decimal","description":"`ttlprp`, the line total.\n\nSage's sum of the five buckets above. **Nothing in this API re-derives or\nchecks it**, so treat it as what was stored rather than as a guaranteed sum."},"userDefined1":{"type":"string","description":"Free text, per install (`usrdf1`)."},"noteText":{"type":"string","description":"Line note (`ntetxt`)."}},"description":"`the underlying record`, a proposal line, as read.\n\nOne row per phase + cost code, priced into five buckets and a total.\n\n**The five buckets are Sage's five preset cost types, as columns rather than as\na code**: `matprp` Material, `labprp` Labor, `eqpprp` Equipment, `subprp`\nSubcontract, `othprp` Other, which is `1` to `5` in the `csttyp` family. There is no\n`costType` column on this table because the cost type *is* which column you\nread. That has a consequence worth knowing before designing against it: cost\ntypes **`6` to `9` are defined per company**, and a proposal line has nowhere to\nput them. An install that uses them will find that money folded into\n`otherProposal` or absent.\n\nThe same shape as a budget line's cost buckets with the hours bucket dropped (a\nproposal prices work, it does not schedule it), and `subProposal` is abbreviated\nthe way `BudgetLine.subBudget` is, for the column Sage labels \"Subcontract\".\n\nAs on `the underlying record`, `recordNumber` is **not** unique to a line: Sage keys `the underlying record`\non the parent's `recnum`, so line identity is `recordNumber` + `phaseNumber` +\n`lineNumber`.\n\nEvery decimal is a JSON **string** at native scale. The v2 spec declared them\n`\"type\": \"number\"` while the wire carried a string."},"PurchaseOrder":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"orderNumber":{"type":"string","description":"`ordnum`, Sage's \"Order#\". A caller-facing document number, not an integer."},"orderDate":{"type":"string","format":"date","description":"`orddte`. A date, not an instant: v2 returned `2026-01-15T00:00:00Z`."},"orderedByEmployeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`odrdby`, a foreign key to ``recnum``. Query the employee lookup.\n\n**`orderedBy` in v2**, beside a `userName` that really is a string. This is\nan integer record number, not a name."},"description":{"type":"string","description":"`dscrpt`."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``. Install-specific rather than a\nfixed set; query the tax-district lookup."},"approvalDate":{"type":"string","format":"date","description":"`appdte`, Sage's \"Approved\"."},"scheduledDate":{"type":"string","format":"date","description":"`schdte`."},"deliveryDate":{"type":"string","format":"date","description":"`deldte`."},"deliveryVia":{"type":"string","description":"`delvia`, Sage's \"Via\"."},"orderTerms":{"type":"string","description":"`ordtrm`, Sage's \"Terms\"."},"orderType":{"type":"integer","format":"int16","description":"`ordtyp`, a foreign key to `PCHTYP.recnum` (Purchase Order Types).\n\nInstall-specific, so not an enum. **There is no lookup endpoint for `PCHTYP`\ntoday**, so a caller currently has no way to resolve this code through the\nAPI."},"status":{"$ref":"#/components/schemas/PurchaseOrderStatus","description":"`status`. Six values; see `PurchaseOrderStatus`."},"attention":{"type":"string","description":"`attion`."},"addressLine1":{"type":"string","description":"`addrs1`."},"addressLine2":{"type":"string","description":"`addrs2`."},"cityName":{"type":"string","description":"`ctynme`."},"stateCode":{"type":"string","description":"`state_`."},"zipCode":{"type":"string","description":"`zipcde`."},"userDefined1":{"type":"string","description":"`usrdf1`."},"userDefined2":{"type":"string","description":"`usrdf2`."},"userName":{"type":"string","description":"`usrnme`."},"receivedTotal":{"$ref":"#/components/schemas/Decimal","description":"`rcvdte`, Sage `Numeric(9,2)`. The value of what has been received.\n\nAn amount here, unlike the line column of the same name, which is a\nquantity: the header value is the sum of each line's received quantity times\nits unit price. One Sage column name, two units, one table apart."},"currentTotal":{"$ref":"#/components/schemas/Decimal","description":"`currnt`, Sage `Numeric(9,2)`. An amount."},"canceledTotal":{"$ref":"#/components/schemas/Decimal","description":"`cancel`, Sage `Numeric(9,2)`. An amount."},"subtotalAmount":{"$ref":"#/components/schemas/Decimal","description":"`subttl`."},"salesTaxAmount":{"$ref":"#/components/schemas/Decimal","description":"`slstax`."},"purchaseTotal":{"$ref":"#/components/schemas/Decimal","description":"`pchttl`, Sage's \"Total\"."},"purchaseBalance":{"$ref":"#/components/schemas/Decimal","description":"`pchbal`, Sage's \"Balance\"."},"enteredDate":{"type":"string","format":"date","description":"`entdte`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``.\n\n**`vendorRecordNumber` in v2 on this model only.** Renamed to `vendorNumber`,\nwhich is what the other nine models carrying `vndnum` call it."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``.\n\n**`jobRecordNumber` in v2 on this model only.** Renamed to `jobNumber` for\nthe same reason as `vendorNumber`."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, a foreign key to ``phsnum``."},"equipmentNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`eqpmnt`, a foreign key to ``recnum``."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a purchase order, as read.\n\n**Every field except `id` and `recordNumber` is optional**, because a v3 query\nmay restrict the response with `select`. That is true of every read entity in\nthis spec: optionality here describes what the caller asked for, not what Sage\nstores.\n\nTwo things to know before migrating from v2:\n\n- **The identifier spellings changed.** This was the one model that spelled\n`jobnum` as `jobRecordNumber` and `vndnum` as `vendorRecordNumber`, where\nnine other models said `jobNumber` and `vendorNumber`. Both are aligned\nbelow.\n- **`lines` is new to the spec, not new to the server.** The collection has\nalways existed at runtime; nothing in v2 described it.\n\nNot here: v2's `initialReceived`. Nothing on the read path ever populated it,\nso it was published and always empty; v3 does not carry it."},"PurchaseOrderInsertInput":{"type":"object","required":["orderNumber","vendorNumber","orderDate","description","lines"],"properties":{"userName":{"type":"string","description":"The Sage user the order is attributed to."},"orderNumber":{"type":"string","minLength":1,"description":"The purchase order number. Caller-supplied; like `transactionNumber` on a\nledger transaction, not validated for uniqueness by OPDC. Reusing one for\nthe same vendor replays the existing order when the lines are identical and\nfails when they are not."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber"},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"Job this order is for, if any."},"orderDate":{"type":"string","format":"date","description":"Date-only, `YYYY-MM-DD`."},"description":{"type":"string","minLength":1},"status":{"type":"integer","format":"int16","description":"``status``. A plain integer rather than a published enumeration,\nbecause the member set for purchase orders created through this endpoint is\nnot confirmed. Absent means `1`; v2 required this field outright with no\ndefault.","default":1},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderInsertLineInput"},"minItems":1}},"description":"A purchase order to create."},"PurchaseOrderInsertLineInput":{"type":"object","required":["lineNumber","partDescription","lineQuantity","linePrice","extendedTotal","accountNumber"],"properties":{"lineNumber":{"type":"integer","format":"int32","minimum":1},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The part's Sage record number. Spelled `partNumber` in v2, which was the\nmisleading name. Absent for a line with no inventory part."},"partDescription":{"type":"string","description":"Part or item description."},"lineQuantity":{"$ref":"#/components/schemas/Decimal","description":"Ordered quantity."},"linePrice":{"$ref":"#/components/schemas/Decimal","description":"Unit price."},"extendedTotal":{"$ref":"#/components/schemas/Decimal","description":"`lineQuantity * linePrice`. Unlike `InventoryAllocationLineInput.total`,\n**not** cross-checked against its factors: the value you send is the value\nstored, and the order's subtotal is the sum of these figures rather than a\nrecomputation from quantity and price.\n\nThat is deliberate rather than pending: this endpoint has no established\ncaller, so no rounding convention has been observed, and refusing an order\nover a disagreement in the third decimal place would be this service\ninventing an accounting rule. Send a figure you are willing to have stored."},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"GL account to distribute this line to."},"subAccountNumber":{"type":"integer","format":"int64"},"inventoryLocation":{"type":"integer","format":"int32","description":"Inventory location this line draws from, if any."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Query the cost-code lookup rather than hardcoding.\n\nv2 sent this as a JSON **number**, so a cost code written in Sage as\n`01.100` arrived over the wire as `1.100`, and the leading zero, which is\npart of the code, was silently lost to numeric encoding. In v3 it is a JSON\nstring end to end, so that loss cannot recur."},"costType":{"type":"integer","format":"int16","minimum":1,"description":"Install-specific above 5. Query the cost-type lookup rather than hardcoding."}},"description":"One purchase order line to create.\n\n`the underlying record` in Sage. A line either draws inventory (`inventoryLocation`) or\ncodes to a job (`costCode`/`costType` against the header's `jobNumber`) or\nneither (a straight GL distribution to `accountNumber`)."},"PurchaseOrderInsertLineResult":{"type":"object","required":["id","recordNumber","lineNumber","partDescription","lineQuantity","linePrice","extendedTotal","accountNumber"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber"},"partDescription":{"type":"string"},"lineQuantity":{"$ref":"#/components/schemas/Decimal"},"linePrice":{"$ref":"#/components/schemas/Decimal"},"extendedTotal":{"$ref":"#/components/schemas/Decimal"},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber"},"subAccountNumber":{"type":"integer","format":"int64"},"inventoryLocation":{"type":"integer","format":"int32"},"costCode":{"$ref":"#/components/schemas/Decimal"},"costType":{"type":"integer","format":"int16"}},"description":"A purchase order line as written."},"PurchaseOrderInsertResult":{"type":"object","required":["id","recordNumber","orderNumber","vendorNumber","status","lines"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"orderNumber":{"type":"string"},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber"},"jobNumber":{"$ref":"#/components/schemas/RecordNumber"},"status":{"type":"integer","format":"int16"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderInsertLineResult"}}},"description":"A purchase order as written, header and lines."},"PurchaseOrderInsertWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun","period","year"],"properties":{"document":{"$ref":"#/components/schemas/PurchaseOrderInsertResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into."}},"description":"The body of a successful `POST /purchase-orders`.\n\n`ledgerTransaction` is **always absent** and `jobCosts` and `inventory` are\n**always empty**: a purchase order is a commitment, not an accounting event, so\nnothing posts, nothing is costed to a job and no stock moves. All of that\nhappens when the order is received, at `POST /purchase-order-receipts`.\n\n`period` and `year` are the fiscal period the **order date** falls in. Note\nthat this is the one endpoint where `WriteResult`'s description of those two\nfields (the period the server \"posted into\") does not apply, because nothing\nwas posted into any period. They are reported because the envelope requires\nthem, and creating an order is deliberately **not** gated on the ledger's open\nperiod: purchase orders are routinely dated forward."},"PurchaseOrderLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"The parent purchase order's `id` (`_idref`), which is the column the child\nfetch joins on.\n\nKept under Sage's own spelling. It is redundant when the line is read nested\nunder its parent, which is the only way it can currently be read, but the\ncolumn is in the child `SELECT` and therefore on the wire."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, a foreign key to ``recnum``.\n\n**`partNumber` in v2**, a name that reads as the alphanumeric part code and\nis not one: this is a record number. Matches `Part.recordNumber`; the\nhuman-facing code is `partCode`."},"partDescription":{"type":"string","description":"`prtdsc`."},"partCode":{"type":"string","description":"The alphanumeric part code a human reads. **`alphaNumber` in v2.**"},"unitDescription":{"type":"string","description":"`untdsc`."},"lineQuantity":{"$ref":"#/components/schemas/Decimal","description":"`linqty`, Sage `Numeric(7,4)`."},"linePrice":{"$ref":"#/components/schemas/Decimal","description":"`linprc`, Sage `Numeric(8,6)`.\n\nSix decimal places, which is why v2's write-side two-place formatting was\nlossy on this field specifically."},"extendedTotal":{"$ref":"#/components/schemas/Decimal","description":"`extttl`, Sage `Numeric(9,2)`. Quantity times price."},"receivedQuantity":{"$ref":"#/components/schemas/Decimal","description":"Quantity received to date. **`receivedAmount` in v2, which is wrong: this is\na quantity, not an amount.**\n\nSage types the column at the same scale as `lineQuantity`, not at the money\nscale, and the receipt cascade adds a received quantity to it. The header's\nsimilarly-named `receivedTotal` *is* money."},"currentQuantity":{"$ref":"#/components/schemas/Decimal","description":"`currnt`. **`currentAmount` in v2.** Renamed on the same grounds as\n`receivedQuantity`: Sage types this column at the quantity scale, while the\nheader column of the same name is at the money scale."},"canceledQuantity":{"$ref":"#/components/schemas/Decimal","description":"`cancel`. **`canceledAmount` in v2.** A quantity, on the same scale evidence\nas `currentQuantity`."},"gstSubject":{"type":"string","description":"`gstsbj`. Sage types this `Yes/No` and the wire carries a string, so the\nvalue domain is unverified. Published as declared rather than promoted to a\n`boolean`, because nothing in the read path converts it."},"pstSubject":{"type":"string","description":"`pstsbj`. Same `Yes/No`-as-string caveat as `gstSubject`."},"hstSubject":{"type":"string","description":"`hstsbj`. Same `Yes/No`-as-string caveat as `gstSubject`."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract and `5` Other are\nSage presets. **`6` to `9` are defined per company** (at one verified\ninstall they are Fuel, Travel, Meals and Lodging), so this is deliberately\nnot an enum. Query the cost-type lookup for the install's labels."},"inventoryLocationNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`invloc`, a foreign key to ``recnum``. Query the inventory-location\nlookup.\n\n**`inventoryLocation` in v2.** Suffixed because it is a record number and the\nbare name reads as a place."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"`lgract`, a foreign key to ``recnum``."},"subAccount":{"type":"integer","format":"int64","description":"`subact`, a foreign key to ``recnum``.\n\n**`subaccount` in v2 on this model.** The same column was spelled four ways\nacross v2's models: `subAccount`, `subaccount`, `subaccountNumber` and\n`subsidiaryType`; `subAccount` is the v3 spelling everywhere."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a purchase order line, as read.\n\n**This schema has never been published.** No v2 reader could discover these\nfield names even after guessing the array existed.\n\nNote before mapping the money columns: `receivedAmount` in v2 **is not an\namount**; see `receivedQuantity` below."},"PurchaseOrderReceiptInput":{"type":"object","required":["purchaseOrderRecnum","transactionDate","shippingNumber","description","vendorNumber","lines"],"properties":{"purchaseOrderRecnum":{"$ref":"#/components/schemas/RecordNumber","description":"The purchase order being received against, ``recnum``. Sent in the body\nrather than in the path."},"userName":{"type":"string","description":"The Sage user the receipt is attributed to."},"transactionDate":{"type":"string","format":"date","description":"Date-only, `YYYY-MM-DD`. Drives the posting period. Unlike\n`/ledger-transactions`, this endpoint accepts no `period` / `year` override."},"shippingNumber":{"type":"string","minLength":1,"description":"Shipping/receiving number. Written to the generated AP invoice's\n``shpnum`` **and** its ``invnum``: the same value doubles as\nthe invoice's own document number."},"description":{"type":"string","minLength":1,"description":"Flows to the AP invoice, the ledger transaction, and (truncated to 30 characters) every job cost this receipt writes."},"userDefined1":{"type":"string","description":"Free text (e.g. delivery location). Flows to the AP invoice's `usrdf1` only."},"status":{"$ref":"#/components/schemas/PurchaseOrderStatus","description":"The purchase order's status after this receipt. Absent means `1`, Open.","default":1},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The vendor this order is with. Flows to the AP invoice and to every job cost\nthis receipt writes. Spelled `vendorRecordNumber` in v2."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The job this order is for. Required for a job cost to be written: lines whose\naccount is a direct-expense account produce one `the underlying record` row each when this\nis present, none when it is absent. Spelled `jobRecordNumber` in v2."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderReceiptLineInput"},"minItems":1}},"description":"A purchase order receipt: receiving quantities against one or more existing\n`the underlying record` lines. **Not a full PO replace**: header fields other than\n`status` and the receipt bookkeeping below are untouched, and the operation\nonly ever adds to what a line has already received."},"PurchaseOrderReceiptInvoiceLineResult":{"type":"object","required":["id","recordNumber","lineNumber","quantity","linePrice","extendedTotal"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber"},"quantity":{"$ref":"#/components/schemas/Decimal"},"linePrice":{"$ref":"#/components/schemas/Decimal"},"extendedTotal":{"$ref":"#/components/schemas/Decimal"}},"description":"One AP invoice line generated by this receipt."},"PurchaseOrderReceiptInvoiceResult":{"type":"object","required":["id","recordNumber","invoiceTotal","lines"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"invoiceTotal":{"$ref":"#/components/schemas/Decimal"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderReceiptInvoiceLineResult"}}},"description":"The AP invoice (`the underlying record`) this receipt generates. Every receipt produces\nexactly one; this is not optional the way `ledgerTransaction` is on other\nendpoints, so it is not modelled as one of `WriteResult`'s optional slots."},"PurchaseOrderReceiptLineInput":{"type":"object","required":["poLineId","lineNumber","updateQuantity","linePrice","accountNumber"],"properties":{"poLineId":{"type":"string","minLength":1,"description":"The existing `the underlying record` row to receive against, its `_idnum`.\n\nA `poLineId` that does not match an existing `the underlying record` row is **silently\nskipped**, not rejected: only lines that can be found are updated, and the\nresponse reports what was actually received.\n\nv2's parallel `poLineIdRef` field does not exist in v3; it was never read."},"lineNumber":{"type":"integer","format":"int32","minimum":1},"updateQuantity":{"$ref":"#/components/schemas/Decimal","description":"Quantity received against this line on this receipt. **Added** to\n`the underlying record`'s running received quantity, not set to it, so replaying this\nrequest without an `Idempotency-Key` double-counts the receipt. Must be\ngreater than zero."},"partNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The part's Sage record number. Triggers inventory updates when present on a\ndedicated-inventory account.\n\nNote the name: the read surface publishes the same value as\n`partRecordNumber`."},"partDescription":{"type":"string","description":"Free text. Flows to the AP invoice line only."},"alphaNumber":{"type":"string","description":"The alphanumeric part code a human reads. Flows to the AP invoice line only.\nThe read surface publishes the same value as `partCode`."},"unitDescription":{"type":"string","description":"Flows to the AP invoice line only."},"linePrice":{"$ref":"#/components/schemas/Decimal","description":"Must be non-negative."},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"``lgract``. Determines expense type and flows to the AP invoice line;\nalso the account debited on the ledger transaction this receipt posts.\n\nSpelled `ledgerAccount` on ledger transaction lines; the two are the same\nkind of value under different names."},"subAccountNumber":{"type":"integer","format":"int64","description":"``subact``, the sub-account for this line's distribution."},"inventoryLocation":{"type":"integer","format":"int32","description":"``invloc``. Flows to the AP invoice line and, when the line has a part and a dedicated-inventory account, to the inventory movement."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Install-specific. Query the cost-code lookup rather than hardcoding.\n\n**Accepted but currently inert.** Every job cost this receipt writes uses\nthe cost code and cost type of the *first* line already on the purchase\norder, never this field's value on the line that actually triggered the job\ncost."},"costType":{"type":"integer","format":"int16","description":"Install-specific above 5. Same caveat as `costCode`: currently inert."}},"description":"One PO line being received on this receipt."},"PurchaseOrderReceiptResult":{"type":"object","required":["purchaseOrder","invoice"],"properties":{"purchaseOrder":{"$ref":"#/components/schemas/PurchaseOrderResult"},"invoice":{"$ref":"#/components/schemas/PurchaseOrderReceiptInvoiceResult"}},"description":"The document this endpoint writes: the updated PO header plus the invoice it generated."},"PurchaseOrderReceiptWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun","period","year"],"properties":{"document":{"$ref":"#/components/schemas/PurchaseOrderReceiptResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into."}},"description":"The body of a successful `POST /purchase-order-receipts`.\n\nReceiving against an existing purchase order updates `the underlying record` / `the underlying record`,\nalways creates an AP invoice (`the underlying record`/`the underlying record`) and a ledger transaction\n(`the underlying record`/`the underlying record`, with `the underlying record`/`the underlying record`/`the underlying record` balance propagation), and\nconditionally writes job costs (`the underlying record`, when `jobNumber` is present) and\ninventory movements (`the underlying record`/`the underlying record`/`the underlying record`).\n\n`inventory[]` is reported only for lines that carry a part **and** whose\naccount is a dedicated-inventory account (``dedinv``), which is a\nper-installation setting. An empty array means no line met that condition, not\nthat the endpoint declined to report."},"PurchaseOrderResult":{"type":"object","required":["id","recordNumber","status","receivedTotal"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"status":{"$ref":"#/components/schemas/PurchaseOrderStatus"},"receivedTotal":{"$ref":"#/components/schemas/Decimal","description":"``rcvdte``, recalculated across **every** line on the order (not just\nthe ones this receipt touched) as the sum, over every line, of\n`line.updateQuantity * line.linePrice`. Renamed from v2's `receivedDate`: despite the name, the\nv2 field was never a date: it is this running received-value total, and\nthe v2 name cost the reader a double-take every time."}},"description":"The purchase order header after this receipt is applied."},"PurchaseOrderStatus":{"type":"integer","format":"int16","description":"``status``, against Sage's \"About purchase order status\".\n\n**Six values, not five. There is no Partial and no Received status.** Earlier\nv2 documentation described `2` as partial and `3` or `4` as received; none of\nthose readings is correct.\n\nNothing branches on status in the receipt cascade (it runs identically for\nevery value), so these codes are descriptive rather than behavioural, unlike\n`LedgerTransactionStatus`.","x-enum-descriptions":["Open. Approved by someone with authority to approve purchase orders.","Review.","Dispute.","Closed. All items have been purchased or canceled.","Void.","Master. Tracks all materials to be purchased from the vendor for an entire project."],"x-enum-varnames":["Open","Review","Dispute","Closed","Void","Master"],"enum":[1,2,3,4,5,6]},"QueryRequestAssembly":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["parts"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestBankReconciliation":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestBase":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."}},"description":"A query request over an entity with no child collections.\n\n**`filter`, singular, does not exist in v3.** v2 accepted it beside `filters`\nas backwards compatibility. Sending it now returns 400 naming the field, which\nis the point of unknown-field rejection: a legacy key dropped in silence is a\nsilently unfiltered result set."},"QueryRequestBudget":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestChangeOrder":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestCostToComplete":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestCreditCardReconciliation":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestHoursToComplete":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestInventoryAllocation":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestLedgerTransaction":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestPayableInvoice":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestProgressBilling":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestProposal":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestPurchaseOrder":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestReceivableInvoice":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestServiceInventory":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestSubcontract":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryRequestUnitsComplete":{"type":"object","properties":{"select":{"type":"array","items":{"type":"string"},"description":"Fields to return. Omit for every field on the entity.\n\nDotted paths address child fields: `lines.extendedTotal`. The selection\napplies to child collections as well as to the parent.\n\nEvery field on every read entity is therefore declared optional except `id`\nand `recordNumber`: a `select` narrower than the model is the normal case,\nnot an error."},"filters":{"$ref":"#/components/schemas/FilterNode"},"orderBy":{"type":"array","items":{"type":"string"},"description":"Sort keys, `\"<field> ASC|DESC\"`.\n\n**The server always appends a unique tiebreak**, so `orderBy` is a prefix of\nthe real ordering rather than the whole of it, and paging is stable even when\nyou supply no sort key at all. In v2, paging without `orderBy` left the\nordering unconstrained, so a client paging a large entity could receive the\nsame row twice and never receive another.","examples":[["unitCost DESC"]]},"page":{"$ref":"#/components/schemas/PageRequest"},"lastUpdated":{"type":"string","format":"date","description":"Return only rows whose **modification** date is at or after this date.\n\nSupported only on entities that record one. Sage does not maintain a general\nmodified timestamp (across roughly 100 models exactly two carry an `lstupd`\ncolumn), so an entity with no such column returns **501**\n(`CHANGE_STAMP_UNSUPPORTED`) naming this field, rather than silently\nreturning everything, which is what v2 did on all 79 entities while the\npublic documentation called it \"the field to build incremental syncs on\".\nComparison is inclusive and the finest granularity available is a whole day."},"createdSince":{"type":"string","format":"date","description":"Return only rows **entered** at or after this date.\n\nA separate field, never a fallback for `lastUpdated`: it catches records\ncreated since the date and does not catch later edits to older records,\nbecause Sage never rewrites the entered date. Supported on 14 entities where\n`lastUpdated` is supported on 7; the same 501 applies elsewhere."},"children":{"type":"array","items":{"type":"string","enum":["lines"]},"description":"Child collections to include. Replaces v2's `withChildren: true`, which\nreturned all of them or none.\n\nThe alias is almost always `lines`. `assemblies` is the one exception and\nuses `parts`, which is defensible and was undiscoverable. Enumerating the\nvalid values per entity is what makes it discoverable now.\n\nChildren are fetched with one query per relation over the parent page's ids,\nso there is no N+1. There is also no bound: a page of 100 purchase orders\npulls every line of all 100. Ask for the collections you need."}},"description":"A query request over an entity that exposes child collections.\n\n`Children` is the set of child-collection names this entity has, so the valid\nvalues are enumerated per entity instead of being offered as a boolean that\nmeans \"all of them, unpaginated, ignoring `select`\"."},"QueryResponseAssembly":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Assembly"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseAssemblyClass":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AssemblyClass"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseBankReconciliation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/BankReconciliation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseBankingTransaction":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/BankingTransaction"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseBenefit":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Benefit"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseBudget":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Budget"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseCardIssuer":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CardIssuer"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseChangeOrder":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ChangeOrder"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseClient":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Client"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseCostCode":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CostCode"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseCostDivision":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CostDivision"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseCostToComplete":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CostToComplete"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseCostType":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CostType"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseCreditCardReconciliation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CreditCardReconciliation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseDailyPayroll":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/DailyPayroll"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseEmployee":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Employee"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseEmployeeEarnings":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/EmployeeEarnings"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseEquipment":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Equipment"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseEquipmentCost":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/EquipmentCost"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseEquipmentRevenue":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/EquipmentRevenue"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseEquipmentType":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/EquipmentType"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseHistoricalAverageCost":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/HistoricalAverageCost"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseHoursToComplete":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/HoursToComplete"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventoryAllocation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryAllocation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventoryBalance":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryBalance"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventoryConsumed":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryConsumed"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventoryHistory":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryHistory"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventoryLocation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryLocation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventoryQuantity":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryQuantity"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseInventorySerial":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InventorySerial"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseJob":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Job"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseJobCost":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/JobCost"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseJobPaygroup":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/JobPaygroup"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseJobPhase":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/JobPhase"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseJobType":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/JobType"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseLedgerAccount":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerAccount"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseLedgerBalance":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerBalance"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseLedgerSetup":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerSetup"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseLedgerSubBalance":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerSubBalance"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseLedgerSubaccount":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerSubaccount"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseLedgerTransaction":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/LedgerTransaction"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePart":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Part"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePartClass":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PartClass"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePayableInvoice":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PayableInvoice"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePayablePayment":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PayablePayment"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePaygroup":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Paygroup"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePayrollCalculation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PayrollCalculation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePayrollPosition":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PayrollPosition"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePayrollPosting":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PayrollPosting"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePayrollRecord":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PayrollRecord"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePeriodStatus":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PeriodStatus"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePreLien":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PreLien"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseProgressBilling":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ProgressBilling"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseProposal":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Proposal"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponsePurchaseOrder":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrder"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseReceivableInvoice":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceivableInvoice"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseReceivablePayment":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceivablePayment"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseSecondaryPayee":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SecondaryPayee"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServiceContract":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceContract"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServiceEquipment":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceEquipment"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServiceInventory":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceInventory"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServiceLocation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceLocation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServicePayment":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServicePayment"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServiceSchedule":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceSchedule"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseServiceType":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ServiceType"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseSubChangeOrderLine":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SubChangeOrderLine"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseSubcontract":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Subcontract"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseTaxDistrict":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaxDistrict"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseTaxEntity":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaxEntity"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseTimecardCalculation":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TimecardCalculation"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseTimecardLine":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TimecardLine"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseTransactionSource":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TransactionSource"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseUnitsComplete":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/UnitsComplete"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendor":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Vendor"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorActivity":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorActivity"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorBalance":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorBalance"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorCertificate":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorCertificate"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorContact":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorContact"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorPartPrice":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorPartPrice"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorRemittance":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorRemittance"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseVendorType":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/VendorType"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"QueryResponseWorkersComp":{"type":"object","required":["asOf","page","data"],"properties":{"asOf":{"type":"string","format":"date-time","description":"The server's clock at the moment the result set was produced, RFC 3339 in\nUTC. Safe to store as a watermark for the next call; the client's clock is\nnot, and v2 gave a client nowhere else to get one."},"page":{"$ref":"#/components/schemas/PageResponse"},"data":{"type":"array","items":{"$ref":"#/components/schemas/WorkersComp"},"description":"Renamed from `response`."}},"description":"A query response. One envelope for all 79 query routes.\n\n`responseType` is gone: a business failure is a status code and a `Problem`\nbody, not an enum inside a 200. On a read that enum was the worse half of the\ndefect: `{\"responseType\": \"ERROR\", \"response\": []}` is indistinguishable from\nan empty result to any client that does not branch on the envelope, and a\nreport built on it is quietly wrong."},"ReceivableInvoice":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"invoiceNumber":{"type":"string","description":"`invnum`, the invoice number, free text rather than a Sage record number."},"invoiceDate":{"type":"string","format":"date","description":"`invdte`. A date, not an instant."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``. The client is reached through it; see the model note."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, the job phase.\n\n**Typed `int64`, where `PayableInvoice.phaseNumber` is `RecordNumber`.** Sage\nwrites `0` for \"no phase\", which a `@minValue(1)` type would reject on an\notherwise legitimate row."},"description":{"type":"string","description":"`dscrpt`."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``. Install-specific rather than a\nfixed set; query the tax-district lookup."},"clientPO":{"type":"string","description":"`pchord`, the client's own purchase order number as it appears on the\ninvoice: free text, **not** a link to a `the underlying record` row.\n\nThe same column serves three roles across the read surface: `clientPO` here\nand on `the underlying record`, `orderNumber` on a ledger transaction, `purchaseOrder` on a\npayable invoice. Genuinely different data, so the names correctly differ, and\n`clientPO` is the spelling `Job` already uses for this exact role."},"dueDate":{"type":"string","format":"date","description":"`duedte`."},"referenceNumber":{"type":"string","description":"`refnum`."},"discountDate":{"type":"string","format":"date","description":"`dscdte`, the date the early-payment discount expires. A real date, unlike `Vendor.discountDateTerms`."},"invoiceType":{"$ref":"#/components/schemas/ApInvoiceType","description":"`invtyp`. `1` Contract, `2` Memo.\n\nThe same verified two-member family as ``invtyp``, which is why this\nreuses `ApInvoiceType` rather than declaring a second scalar. The distinction\nis load-bearing: a Contract invoice increases the job balance and a Memo one\ndoes not."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`: `1` Open, `2` Review, `3` Dispute, `4` Paid,\n`5` Void.\n\nThe same five integers as an AP invoice's status, with `3` meaning disputed\nby the client rather than by the vendor. Published as an integer rather than\nas a named type, and it must not be treated as interchangeable with the AP\nstatus family even though the members currently agree."},"userDefined1":{"type":"string","description":"`usrdf1`."},"userDefined2":{"type":"string","description":"`usrdf2`."},"discountAmount":{"$ref":"#/components/schemas/Decimal","description":"`dscavl`, the discount available.\n\n**`discountAvail` in v2**, an abbreviation Sage does not use. Aligned to the\n`discountAmount` spelling `the underlying record` already publishes, so `dscavl` now has one\nname on the read surface."},"discountTaken":{"$ref":"#/components/schemas/Decimal","description":"`dsctkn`, the discount actually taken.\n\n**`discCredit` in v2**, an abbreviation Sage does not use. `discountTaken` is\nthe spelling the payment entities already publish for this column."},"retention":{"$ref":"#/components/schemas/Decimal","description":"`retain`, the retainage held on this invoice. An amount, not the rate ``rtnrte`` carries."},"salesTax":{"$ref":"#/components/schemas/Decimal","description":"`slstax`, the sales tax charged."},"taxableAmount":{"$ref":"#/components/schemas/Decimal","description":"`taxabl`, the portion of the invoice that sales tax was computed on.\n\n**`taxable` in v2, and renamed because the line publishes a flag under that\nexact name.** One Sage column carries a flag on the line and an amount here;\nafter the rename the two names track the two roles exactly."},"amountPaid":{"$ref":"#/components/schemas/Decimal","description":"`amtpad`."},"totalPaid":{"$ref":"#/components/schemas/Decimal","description":"`ttlpad`."},"subtotalAmount":{"$ref":"#/components/schemas/Decimal","description":"`subttl`."},"invoiceTotal":{"$ref":"#/components/schemas/Decimal","description":"`invttl`."},"invoiceNet":{"$ref":"#/components/schemas/Decimal","description":"`invnet`."},"invoiceAmount":{"$ref":"#/components/schemas/Decimal","description":"`invamt`."},"invoiceBalance":{"$ref":"#/components/schemas/Decimal","description":"`invbal`. What remains unpaid."},"period":{"type":"integer","format":"int16","description":"The period the invoice posted into (`actper`). Sage derives it from\n`enteredDate`."},"year":{"type":"integer","format":"int16","description":"`postyr`, the posting year that goes with `period`."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this invoice posted, or `0` when\nno journal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason."},"userName":{"type":"string","description":"`usrnme`, the Sage user who entered the invoice."},"enteredDate":{"type":"string","format":"date","description":"`entdte`, the date the invoice was entered.\n\nThis is the column `createdSince` filters on for this entity. It is written\nonce and never rewritten, which is why `lastUpdated` on a receivable invoice\nis an explicit 501 rather than a silent approximation."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ReceivableInvoiceLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`.\n\nTwo behaviours to plan for, neither of them stated in v2: children ignore\n`select`, so every column of every line comes back regardless of what was\nasked for on the parent, and children are unpaginated and unfilterable."}},"description":"`the underlying record`, a receivable invoice, as read.\n\nThe AR counterpart of `PayableInvoice` (`the underlying record`), and deliberately mirrored on\nit field for field where the columns agree. Three differences are worth naming\nbefore anyone reads one model as a template for the other:\n\n- **There is no client on this entity.** `the underlying record` carries `jobnum`, not a\nclient foreign key, where `the underlying record` carries `vndnum`. An AR invoice is billed\nthrough its job; resolve the client by reading `the underlying record` and following\n`Job.clientNumber`. A caller looking for `clientNumber` here will not find\none, and its absence is Sage's shape rather than a gap in the read model.\n- **No `voidRecords`.** `the underlying record` does store `vodrec`, as a single `bigint`, but\nthis API does not read the column on this table, so there is nothing for v3 to\npublish. Only `the underlying record` and `the underlying record` expose it.\n- **No holdback, freight, `payee2`, `setToPay`, `batchNumber` or\n`shipmentNumber`.** Those are AP-side columns. What AR has and AP does not is\n`slstax`, `taxdst` and `taxabl`: sales tax is charged, not paid.\n\n`createdSince` is supported and `lastUpdated` is a 501: `the underlying record` carries\n`entdte` and no edit stamp, so the API can answer \"which invoices were entered\nsince my last sync\" and cannot answer \"which were corrected\".\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`, so an absent\nfield means \"not asked for\", not \"not set\". Dates here are dates, not the\nRFC 3339 instants v2 returned for a Sage `Date` column."},"ReceivableInvoiceLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `ReceivableInvoice`.\n\nReturned on every child row: the child SELECT is built from the full column\nmap and ignores `select`, so this comes back even when the row is already\nnested under the parent that it names."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, the part's Sage record number.\n\n**`partNumber` in v2**, a name that reads as the human-facing part code and\nis not one.\n\nThere is no `partCode` on this model, and that is not an omission: `the underlying record`\nhas no alphanumeric part-code column, unlike `the underlying record`. A caller that wants\nthe part code has to read it from `/parts`."},"description":{"type":"string","description":"`dscrpt`, the line's own description.\n\nSpelled `description`, not `partDescription`. The AP line's\n`partDescription` is a different column (``prtdsc``), so these are two\nnames for two things rather than drift."},"unitDescription":{"type":"string","description":"Unit of measure, `untdsc`."},"lineQuantity":{"$ref":"#/components/schemas/Decimal","description":"`linqty`. JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"linePrice":{"$ref":"#/components/schemas/Decimal","description":"`linprc`, the unit price."},"extendedPrice":{"$ref":"#/components/schemas/Decimal","description":"`extprc`, quantity times price for this line.\n\n**Not the same column as the AP line's `extendedTotal`**, which is\n``extttl``. Two columns, two names, correctly."},"taxable":{"type":"integer","format":"int16","description":"`taxabl`, a 0/1 flag: whether this line is subject to sales tax.\n\n**A flag here and an amount on the parent, from the same six-character Sage\ncolumn.** v2 published both under the name `taxable`, so one response carried\n`taxable: 1` on a line and `taxable: \"1450.00\"` on its parent: one name, one\nrequest, two types. v3 renames the parent's to `taxableAmount` and leaves the\nflag here; see `ReceivableInvoice.taxableAmount`.\n\nAn integer rather than a boolean, because the wire carries `0`/`1`\nunconverted."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"The GL revenue account the line credits (`lgract`).\n\n**`accountNumber` in v2**, and this is the last model on the read surface to\ncarry that spelling for this column.\n\nNote the AP line's identically-renamed field is a *different* column\n(``actnum``); the names converge, the columns do not."},"subAccount":{"type":"integer","format":"int64","description":"The sub-account (`subact`), or absent when the line has none.\n\n**`subaccountNumber` in v2.** `subAccount` is the spelling\n`LedgerTransactionLine` and `PayableInvoiceLine` already use."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a receivable invoice line, as read.\n\n**Absent from the published v2 spec entirely**, both the array and this schema,\nso a v2 reader could not discover these field names even after guessing the\ncollection existed.\n\nStructurally close to `PayableInvoiceLine` but **not** column-for-column, and\nthe two places it differs are places where a reader who assumed symmetry would\nbe wrong. See `extendedPrice` and `ledgerAccount`.\n\nAs with the parent, only `id` and `recordNumber` are always present."},"ReceivablePayment":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"description":{"type":"string","description":"`dscrpt`."},"checkNumber":{"type":"string","description":"`chknum`, the check number.\n\n**A string, not an integer**, and Sage means it: the column holds whatever\nwas written on the instrument, including EFT and wire references. Do not\nparse it as a number."},"checkDate":{"type":"string","format":"date","description":"`chkdte`, the date on the check.\n\nA date, not an instant. v2 returned `2026-01-15T00:00:00Z` for a Sage `Date`\ncolumn. With no change stamp on this table, this is also the only column a\ncaller can use to bound a sync window, and it is a business date chosen by\nwhoever entered the payment, not a row stamp."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, the payment."},"discountTaken":{"$ref":"#/components/schemas/Decimal","description":"`dsctkn`, the early-payment discount the client took."},"appliedCredit":{"$ref":"#/components/schemas/Decimal","description":"`aplcrd`, credit applied to the payment rather than paid in cash."},"period":{"type":"integer","format":"int16","description":"The period the payment posted into (`actper`), with `year`.\n\nThe same `actper`/`postyr` pair the invoices carry, and it is the only\naccounting placement on this record: there is no status column, so a payment\nis either present with a `ledgerRecord` or it is not."},"year":{"type":"integer","format":"int16","description":"`postyr`, the posting year that goes with `period`."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this payment posted, or `0` when\nno journal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason. It is also the\nonly join this entity offers; see the model note."}},"description":"`the underlying record`, a receivable payment: a client's check as Sage records it.\n\n**Eleven columns, no children, and no way to tell what it paid.** `the underlying record` has\nno child collection, and it carries no invoice, client or job foreign key. The\nonly link this entity publishes to the rest of the ledger is `ledgerRecord`.\nAnyone reconciling a payment to the invoices it settled has to go through\n`the underlying record`/`the underlying record`; `POST /receivable-payments/query` cannot answer it, and no\n`children` value will make it.\n\n**Neither `lastUpdated` nor `createdSince` is supported**, and this is the\nstrongest form of that answer rather than an oversight. The table has no\n`edtdte` and no `entdte` column, so there is nothing to filter on. Both filters\nare a 501 naming the table, which is the honest answer: an incremental sync of\npayments is not possible through this API. Poll `checkDate` or sync the ledger\ninstead.\n\n`the underlying record` (payable) and `the underlying record` (service) are the same eleven columns under\ndifferent table names, and all three are modelled separately, as\n`PayablePayment` and `ServicePayment`. Separately, because they are separate\ntables with separate `recnum` sequences: a `recordNumber` that means one row in\nAR and a different row in AP cannot share a resource.\n\nOnly `id` and `recordNumber` are required. Every other field is optional\nbecause a v3 read may restrict the response with `select`, so an absent field\nmeans \"not asked for\", not \"not set\"."},"RecordNumber":{"type":"integer","format":"int64","minimum":1,"description":"A Sage record number: the integer a human sees in Sage.\n\nDistinct from `id`, which is the row's UUID (`_idnum`). Both identify the same\nrow, and both are returned."},"SchemaCompatibility":{"type":"object","required":["state","readsAllowed","writesAllowed","onPrem"],"properties":{"state":{"type":"string","description":"`ok`, `degraded`, `incompatible`, or `unknown`.\n\n**`unknown` means the probe could not run**, not that something is wrong.\nNothing is being refused on the strength of an unknown state."},"readsAllowed":{"type":"boolean","description":"Whether reads are currently permitted against this database."},"writesAllowed":{"type":"boolean","description":"Whether writes are currently permitted against this database."},"probedAt":{"type":"string","description":"When the compatibility probe last ran, if it has run."},"onPrem":{"$ref":"#/components/schemas/OnPremiseBuild","description":"The on-premise build reported by the Windows service."}},"description":"The Sage schema compatibility state, as reported on a ping."},"SecondaryPayee":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``: the payable invoice, published\nas the `PayableInvoice` entity.\n\nNot this row's own number. This is the join a caller uses to get from an\ninvoice to its joint payees."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`. Sequential within the invoice."},"payeeName":{"type":"string","description":"`paynme`, Sage's \"Second Payee\": the name added to the cheque alongside the\nvendor.\n\nFree text, not a foreign key: the joint payee is typically not a vendor in\nthe system. Distinct from `VendorRemittance.payeeName`, which redirects every\npayment to a vendor rather than naming a second party on one invoice."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, `Numeric(9,2)`. What this payee is owed out of the invoice. JSON string, native scale."},"amountPaid":{"$ref":"#/components/schemas/Decimal","description":"`amtpad`, Sage's \"Paid\". What has been paid against `amount` so far."},"balance":{"$ref":"#/components/schemas/Decimal","description":"`balnce`, Sage's \"Balance\": `amount` less `amountPaid`.\n\nStored by Sage, not derived on read, so a caller that finds it disagreeing\nwith the subtraction is looking at Sage's state rather than at a projection\nerror."},"setToPay":{"$ref":"#/components/schemas/Decimal","description":"`setpay`, Sage's \"To Pay\": the amount staged for the next payment run.\n\nAn intent, not a posting: it is what a user has marked in 4-3, and it moves\nto `amountPaid` when the run posts."}},"description":"`the underlying record`, Sage's \"Secondary Payees\", as read.\n\nJoint payees on a payable invoice: the supplier and the lien claimant named on\nthe same cheque. Each row carries its own amount, what has been paid against it\nand what is set to pay in the next run, so the table is a payment plan per\npayee rather than a name list.\n\n**This is an AP *invoice* satellite, not a vendor satellite.** Sage keys the underlying record\non `recnum` targeting ``recnum``, under menu 4-2, and there is no `vndnum`\non the table at all; the vendor is reached through the invoice. Anyone looking\nfor \"the vendor's secondary payees\" has to go through `the underlying record` to find them.\n\n`recordNumber` therefore carries the invoice's number, not this row's, which is\nwhat every child table in the product does and what `SubcontractLine` and\n`PayableInvoiceLine` already publish.\n\n**the underlying record is not registered as a child of `the underlying record`**: that parent's only child\ncollection is its lines (`the underlying record`), so it is read as a top-level entity and\nfiltered on `recordNumber`, not requested through `children`."},"ServiceContract":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"contractNumber":{"type":"string","description":"`connum`, the caller-facing contract number. A string, not an integer."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clnnum`, a foreign key to ``recnum``."},"description":{"type":"string","description":"`dscrpt`."},"startDate":{"type":"string","format":"date","description":"`strdte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"expirationDate":{"type":"string","format":"date","description":"`expdte`, when the agreement lapses."},"contractType":{"type":"integer","format":"int16","description":"`contyp`, a foreign key into the service contract types.\n\nInstall-specific, so **deliberately not an enum**: the members are defined\nper company and are not verified here. Do **not** assume the `1-Open` /\n`2-Review` / `3-Dispute` prefix that several other Sage families share: the\nsequences diverge from position 4 onward. **There is no lookup endpoint for\nthis table today**, so a caller cannot currently resolve the code to a name\nthrough this API."},"contractAmount":{"$ref":"#/components/schemas/Decimal","description":"`conamt`, the contract value. JSON string, native scale."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. The created stamp `createdSince` filters on."},"userName":{"type":"string","description":"`usrnme`, the Sage user who entered the record."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a service contract, as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\nNot to be confused with `the underlying record`, the subcontract: that one is a commitment\nmade to a vendor, this one is a recurring-service agreement with a client. The\ntwo share three field names (`contractNumber`, `contractType`, `description`)\nover different Sage columns (`connum` here, `ctcnum` there), so a value read\nfrom one is not comparable with the same-named value from the other.\n\n`entdte` is a created stamp, so `createdSince` is honoured on this entity.\n`lastUpdated` is a 501: Sage keeps no modification stamp on the table. Note\nthat `svedte`, which some Sage tables carry, is a business date chosen by the\nperson entering the record rather than a row stamp, so it is never treated as\none."},"ServiceEquipment":{"type":"object","required":["id","clientNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: the **client's** record number, a foreign key to ``recnum``,\nnot this row's identity.\n\n**Required, in the slot `recordNumber` occupies on every other read model.**\n`the underlying record` is keyed (`recnum`, `eqpnum`), so this column is not unique per row;\nsee `ServiceLocation.clientNumber`, which is the same shape."},"equipmentNumber":{"type":"integer","format":"int64","description":"`eqpnum`, the unit's number **within the client**. Not a record number, which\nis why it is not typed `RecordNumber`.\n\nThe likelier of the two candidate targets for\n`ServiceInventoryLine.equipmentNumber`, though which table that column points\nat is unverified; confirm against your install before joining on it."},"equipmentName":{"type":"string","description":"`eqpnme`."},"manufacturer":{"type":"string","description":"`manfct`. Free text; `Part.manufacturer` is the same concept on the part master."},"serialNumber":{"type":"string","description":"`sernum`. Spelled `srlnum` on `the underlying record`: one concept, two Sage column names, on two tables."},"locationNumber":{"type":"integer","format":"int32","description":"`locnum`, a foreign key to ``locnum``: the client's service location, keyed within the client."},"installedDate":{"type":"string","format":"date","description":"`istdte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a piece of customer-owned equipment under service, as read.\n\n**The customer's equipment, not the company's.** `the underlying record` is the company's own\nequipment register: owned assets with purchase dates, hourly costs and a\ndepreciation trail. `the underlying record` is the air handler at the customer's site that a\nservice call is about. The two share three field names (`equipmentNumber`,\n`equipmentName`, `serialNumber`) over different tables, and the columns behind\n`serialNumber` are not even spelled the same: `sernum` here, `srlnum` on\n`the underlying record`.\n\n**This model has no `recordNumber`, for the same reason `ServiceLocation` has\nnone.** ``recnum`` holds the *client's* record number and is published as\n`clientNumber`; a unit is identified by the pair (`recnum`, `eqpnum`), numbered\nwithin the client. A caller keying on `recordNumber` will not find one.\n\nNo change stamp: `the underlying record` carries none, so `lastUpdated` and `createdSince`\nboth answer 501."},"ServiceInventory":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"orderNumber":{"type":"string","description":"`ordnum`, Sage's work-order number. A caller-facing document number, a string."},"invoiceNumber":{"type":"string","description":"`invnum`, Sage's invoice number. Distinct from `orderNumber`: one document carries both."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clnnum`, a foreign key to ``recnum``."},"orderDate":{"type":"string","format":"date","description":"`orddte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"description":{"type":"string","description":"`dscrpt`."},"taxableExtended":{"$ref":"#/components/schemas/Decimal","description":"`taxext`, the taxable extended total off the lines."},"nonTaxableExtended":{"$ref":"#/components/schemas/Decimal","description":"`ntxext`, the non-taxable extended total off the lines."},"taxableOverride":{"$ref":"#/components/schemas/Decimal","description":"`taxovr`. A manual override of `taxableExtended`, not a second total."},"nonTaxableOverride":{"$ref":"#/components/schemas/Decimal","description":"`ntxovr`. The override counterpart of `nonTaxableExtended`."},"locationNumber":{"type":"integer","format":"int32","description":"`locnum`, a foreign key to ``locnum``: the client's service location, keyed within the client."},"contactName":{"type":"string","description":"`ctcnme`, the site contact. Free text; this API never writes it."},"phoneNumber":{"type":"string","description":"`phnnum`. Free text as Sage stores it; no format is imposed."},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string"},"zipCode":{"type":"string"},"dueDate":{"type":"string","format":"date","description":"`duedte`.\n\n**Never written by this API, deliberately**: there is nothing on a service\ninvoice to derive a due date from, and a guessed date is worse than none. So\n**AR aging has nothing to age against on this entity** until a rule is\nagreed; expect the column at its default on every record."},"purchaseOrder":{"type":"string","description":"`pchord`, the customer's PO reference. Free text, as on `PayableInvoice`."},"referenceNumber":{"type":"string","description":"`refnum`."},"userDefined1":{"type":"string"},"userDefined2":{"type":"string"},"deposit":{"$ref":"#/components/schemas/Decimal","description":"`depost`, a deposit taken against the order."},"salesTax":{"$ref":"#/components/schemas/Decimal","description":"`slstax`, the header sales tax. Typed `Decimal` here and `string` on the line; see `ServiceInventoryLine.salesTax`."},"invoiceDate":{"type":"string","format":"date","description":"`invdte`.\n\nWritten only when a document is posted through the v3 endpoint, where it is\nset from `orderDate`. Anything posted before that carries the column default:\n**a posted invoice with no invoice date**."},"scheduleDate":{"type":"string","format":"date","description":"`schdte`, the date the call is scheduled for.\n\n**The v3 write side spells this same column `scheduledDate`**, so map both\nwhen round-tripping a document."},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`empnum`, the technician assigned to the call.\n\n**`technician` in v2.** Renamed to `employeeNumber`, the single spelling this\ncolumn carries across v3. Note that the salesperson is a *different* Sage\ncolumn on this same record. See `salesperson`."},"salesperson":{"$ref":"#/components/schemas/RecordNumber","description":"`slspsn`. A different Sage column from `empnum`; a record can carry both."},"invoiceType":{"type":"integer","format":"int32","description":"`invtyp`, a foreign key into the service invoice types (`the underlying record`).\n\n**Not a fixed enum**: Sage defines these per company in menu 11-1 and\npublishes only worked examples for a plumbing and an electrical company.\nResolve a code through the `ServiceType` lookup rather than hardcoding it.\nUnrelated to ``invtyp``/``invtyp``, which *is* a fixed enum with a\ndifferent meaning.\n\nWidened to `int32` here; **the v3 write side declares the same column\n`int16`**."},"status":{"type":"integer","format":"int32","description":"`status`, Sage Service Receivables (menu 11-1).\n\n`1` Open, `2` Review, `3` Dispute, `4` Paid, `5` Void, `6` Quote,\n`7` Work Order, `8` Complete, `9` Route, `10` Contract.\n\n**The numbering is not workflow order**: the lifecycle runs\n`6` -> `7` -> (`8` or `1`) -> `4`, and **only `1` (Open) posts to the\ngeneral ledger**. A document at any other status is stored without a journal.\n\nLeft as a plain integer rather than an enum type; note that the write side of\nthis entity declares the same column as an `int16`."},"priority":{"type":"integer","format":"int16","description":"`priort`.\n\n**The meaning of the codes is not established**: no Sage documentation for\nthis column has been found, so no members are published. Read the values off\nyour own install before relying on them."},"taxableLine":{"$ref":"#/components/schemas/Decimal","description":"`taxlin`, the taxable total taken from the lines."},"nonTaxableLine":{"$ref":"#/components/schemas/Decimal","description":"`ntxlin`, the non-taxable total taken from the lines."},"sourceNumber":{"type":"integer","format":"int32","description":"`invsrc`, a foreign key to the **Lead Sources** table (`LEDSRC`).\n\n**This name means something different on the write side of this same entity,\nand it will bite a caller who round-trips a record.** Writing `sourceNumber`\nsteers the posted journal's GL source code (``srcnum``) and never\ntouches `invsrc`; reading `sourceNumber` back returns this lead-source key,\nwhose value has nothing to do with what was sent. **One JSON name, two\nunrelated meanings, on one entity.**\n\n`LEDSRC` is not exposed by this API, so the code cannot currently be resolved\nto a lead-source name either."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``.\n\nNot an enum. **Nothing validates a caller-supplied district against the tax\ndistrict table**, here or on any other table carrying this column, so an\ninvalid code is stored as sent."},"paymentType":{"type":"integer","format":"int16","description":"`pmttyp`. The code's members are not documented; exposed as the integer it\nis, and never written by this API."},"invoiceTotal":{"$ref":"#/components/schemas/Decimal","description":"`invttl`. The figure the journal debits Service Receivables for when the document posts."},"invoiceBalance":{"$ref":"#/components/schemas/Decimal","description":"`invbal`, what remains unpaid.\n\nWritten only when a document is posted through the v3 endpoint, where it is\nset to `invoiceTotal`: on a newly posted invoice with no payments the\noutstanding balance *is* the total. **On anything posted through v2 the\ncolumn carries its default while the general ledger says the customer owes\nthe money**, so do not read a zero here as \"paid\"."},"invoiceNet":{"$ref":"#/components/schemas/Decimal","description":"`invnet`. Written on the v3 posting path only, as `invoiceBalance` is, and set to `invoiceTotal`."},"taxableAmount":{"$ref":"#/components/schemas/Decimal","description":"`taxabl`, the header taxable amount."},"nonTaxableAmount":{"$ref":"#/components/schemas/Decimal","description":"`nontax`, the header non-taxable amount, the direct counterpart of\n`taxableAmount`.\n\n**`nontaxAmount` in v2.**\n\n**Never written by this API, deliberately**, because no input field\nunambiguously identifies the header's non-taxable amount and a wrong figure\nin tax reporting is worse than a blank. Expect the column at its default:\n`taxabl` is populated with no counterpart here."},"period":{"type":"integer","format":"int16","description":"`actper`, the period the document posted into.\n\n**`0` on every non-posting write**, which is the common path: a document is\nstored without posting at any status other than `1` (Open), and the default\nstatus is `7` (Work Order). This column and `year` are only resolved when the\ndocument posts."},"year":{"type":"integer","format":"int16","description":"`postyr`. Absent or `0` with `period`, for the same reason."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this document posted, or `0`\nwhen no journal was written, which is every status other than `1` (Open).\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason, and kept as\n`ledgerRecord` because all fourteen models carrying `lgrrec` agree on the\nspelling."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``. Absent when the call is not job-costed."},"enteredDate":{"type":"string","format":"date","description":"`entdte`. The created stamp `createdSince` filters on."},"invoiceAmount":{"$ref":"#/components/schemas/Decimal","description":"`invamt`. Written on the v3 posting path only, as `invoiceBalance` is, and set to `invoiceTotal`."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ServiceInventoryLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a service receivable (quote, work order or invoice), as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\n**Six columns are commonly left unwritten, and four of them are the AR\nbalance.** `invbal`, `invamt`, `invnet` and `invdte` are populated only when a\ndocument is posted through the v3 service-invoice endpoint. Anything posted\nthrough the v2 endpoint, and every non-posting v3 write, leaves them at\nwhatever the database default is, so a reader can see an AR subledger showing\nnothing outstanding while the general ledger carries the receivable. Nothing is\nbackfilled. **Treat a zero in these four columns as \"never written\", not as\n\"nothing outstanding\".** The other two, `duedte` and `nontax`, are not written\nat all; see `dueDate` and `nonTaxableAmount`.\n\n`lines` is the child collection, requested with `children: [\"lines\"]`.\n`entdte` is a created stamp, so `createdSince` is honoured on this entity;\n`lastUpdated` is a 501, because Sage keeps no modification stamp on the table."},"ServiceInventoryInput":{"type":"object","required":["orderDate"],"properties":{"userName":{"type":"string","description":"The Sage user the document (and, when posted, its journal) is attributed to."},"orderNumber":{"type":"string","description":"``ordnum``, the caller's work-order number. Free text, not validated for uniqueness."},"invoiceNumber":{"type":"string","description":"``invnum``. **Required when `status` is `1` (Open)**, because it becomes\nthe ledger transaction number for the posted journal, and optional\notherwise."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"``clnnum``, the customer being billed (AR sub-ledger). **Required\nwhen `status` is `1` (Open)**."},"orderDate":{"type":"string","format":"date","description":"Drives the posting period/year when `status` is `1` (Open); no separate period/year override exists for this document."},"description":{"type":"string"},"taxableExtended":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"nonTaxableExtended":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"taxableOverride":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"nonTaxableOverride":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"salesTax":{"$ref":"#/components/schemas/Decimal","description":"The sales tax amount. Posts as a credit to `salesTaxAccount` when\nnon-zero and `status` is `1` (Open).","default":"0.00"},"taxable":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"invoiceTotal":{"$ref":"#/components/schemas/Decimal","description":"``invttl``. **Required non-zero when `status` is `1` (Open)**. It is the\ntotal the posted journal must balance to: `invoiceTotal` must equal the\nsigned sum of every line's `extendedPrice` plus `salesTax`. May be negative: a negative total is\na credit memo and posts a reversing journal.","default":"0.00"},"taxDistrict":{"type":"integer","format":"int64","description":"Install-specific. Not an enum; query the tax-district lookup."},"salesTaxAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"Sales Tax Payable GL account. **Required when `status` is `1` (Open) and\n`salesTax` is non-zero**; the credit leg of the posted journal."},"salesTaxSubAccount":{"type":"integer","format":"int64","description":"Optional sub-account for the `salesTaxAccount` credit.","default":0},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The job this work order bills against, if any. When present, one job\ncost is written per line (``wrkord`` set to this document's own\nrecord number) when the document posts; absent means no job costs at all."},"invoiceType":{"type":"integer","format":"int16","description":"Install-specific; query the invoice-type lookup rather than hardcoding.","default":1},"status":{"$ref":"#/components/schemas/ServiceInventoryStatus","description":"Absent means `7` (Work Order), a scheduled call, not yet an accounting\ndocument. See `ServiceInventoryStatus` for what each value does.","default":7},"priority":{"type":"integer","format":"int16","default":3},"taxableLine":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"nonTaxableLine":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"scheduledDate":{"type":"string","format":"date"},"sourceNumber":{"type":"integer","format":"int32","minimum":1,"description":"GL source code stamped on the posted journal's ``srcnum`` only\n(never ``invsrc``, which is an unrelated FK to the Lead Sources\ntable and is left blank). Absent means `22` (Service Management AR sales\ninvoice). **New in v3**: v2 had no such field, so every v2 invoice posts\nwith the default. Install-specific above 33; query the transaction-source\nlookup.","default":22},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ServiceInventoryLineInput"},"description":"The lines to bill. At least one is required when `status` is `1` (Open),\nsince the journal's revenue credit is built from these, and lines may be\nomitted or empty otherwise (e.g. a Quote or Work Order not yet priced)."}},"description":"A service inventory (work order / invoice) to create. `the underlying record` header plus\n`the underlying record` lines."},"ServiceInventoryLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `ServiceInventory`.\n\nReturned on every child row. `select` does not narrow child rows, so this\ncomes back even though the row is already nested under the parent it names."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"partRecordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`prtnum`, the part's Sage record number, a foreign key to ``recnum``,\nnot the alphanumeric code a human reads. That code is `partCode`.\n\n**Spelled `partNumber` in v2.**"},"description":{"type":"string","description":"`dscrpt`."},"partCode":{"type":"string","description":"The alphanumeric part code a human reads (`alpnum`). `alphaNumber` in v2; see `partRecordNumber`."},"unitDescription":{"type":"string","description":"`untdsc`, the unit of measure as text."},"partQuantity":{"$ref":"#/components/schemas/Decimal","description":"`prtqty`. JSON string, native scale."},"partPrice":{"$ref":"#/components/schemas/Decimal","description":"`prtprc`, the unit price. Native scale in both directions; v3 has no `Decimal2`."},"extendedQuantity":{"$ref":"#/components/schemas/Decimal","description":"`extqty`, the extended quantity.\n\n**`extQuantity` in v2**, an abbreviation Sage does not use. The v3 write\nresult for this same entity already spells the column `extendedQuantity`, so\na line now reads back under the name it was written under."},"extendedPrice":{"$ref":"#/components/schemas/Decimal","description":"`extprc`, the extended price. **`extPrice` in v2**; see `extendedQuantity`."},"ticketNumber":{"type":"string","description":"`tktnum`, the service ticket this line came off. Free text, not a record number."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract and `5` Other are\nSage presets; **`6` to `9` are defined per company**, so this is deliberately\nnot an enum. Query the cost-type lookup."},"salesTax":{"type":"string","description":"`slstax`, the line's sales tax.\n\n**Typed as a plain string, not as a decimal.** The same Sage column name on\nthe header (`ServiceInventory.salesTax`) is a decimal; on the line it is\nstored and returned as text, and **no numeric format is guaranteed**. Do not\nassume the value parses as a number without checking it."},"ledgerAccount":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"The GL revenue account the line credits (`actnum`).\n\n**`accountNumber` in v2, and still `accountNumber` on the v3 write result for\nthis entity**: the same column is written under one name and read back under\nanother, so map both when round-tripping a line."},"subAccount":{"type":"integer","format":"int64","description":"The sub-account (`subact`).\n\n**`subaccountNumber` in v2.** `subAccount` is the spelling\n`LedgerTransactionLine` and `PayableInvoiceLine` already use for this column."},"inventoryLocationNumber":{"type":"integer","format":"int32","description":"`invloc`. **`inventoryLocation` in v2**; `PurchaseOrderLine` already publishes this column suffixed."},"serialNumber":{"type":"string","description":"`sernum`. The serialised unit this line moved, when there is one."},"equipmentNumber":{"type":"integer","format":"int64","description":"`eqpnum`, the equipment serviced by this line.\n\n**Which table it points at is unverified.** ``recnum`` is the company's\nown equipment register; ``eqpnum`` is the customer-owned equipment a\nservice call is about, and that one is a per-client sequence rather than a\nrecord number. The second reads as the likelier target for a service line,\nwhich is exactly why it is not typed `RecordNumber` here: a `@minValue(1)`\non a per-client sequence would be a promise nothing checks. Resolve against\nan install before a caller joins on it."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clnnum`, a foreign key to ``recnum``. Denormalised onto the line; the header carries it too."}},"description":"`the underlying record`, a service inventory line, as read.\n\nReturned nested under `ServiceInventory` as the `lines` collection, requested\nwith `children: [\"lines\"]`.\n\n**Several fields are named differently from v2**, where this table was not\npublished at all: `partNumber` is now `partRecordNumber`, `alphaNumber` is\n`partCode`, `accountNumber` is `ledgerAccount`, `subaccountNumber` is\n`subAccount`, `inventoryLocation` is `inventoryLocationNumber`, and\n`extQuantity`/`extPrice` are `extendedQuantity`/`extendedPrice`."},"ServiceInventoryLineInput":{"type":"object","required":["lineNumber"],"properties":{"lineNumber":{"type":"integer","format":"int32","minimum":1},"description":{"type":"string","description":"Truncated to 30 characters when carried onto a job cost (``dscrpt``)."},"unitDescription":{"type":"string"},"partQuantity":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"partPrice":{"$ref":"#/components/schemas/Decimal","default":"0.00"},"extendedQuantity":{"$ref":"#/components/schemas/Decimal","description":"Signed to carry a credit/return. A negative `extendedQuantity` (e.g. a\n\"Millings Credit\" line at `-1`) flips the sign of this line's revenue and\njob-cost impact even though `extendedPrice` is sent as a plain magnitude.\nA caller that already signs `extendedPrice` and leaves this non-negative\ngets the same result either way.","default":"0.00"},"extendedPrice":{"$ref":"#/components/schemas/Decimal","description":"The line's sale amount. See `extendedQuantity` for the credit/return sign convention.","default":"0.00"},"costType":{"type":"integer","format":"int32","description":"Install-specific above 5, like `JobCostInput.costType`; query the\ncost-type lookup rather than hardcoding. Used only to build this line's\njob cost when the header's `jobNumber` is set; defaults to `1` there,\nnot here, when omitted."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"Job cost code. Used only to build this line's job cost when the header's\n`jobNumber` is set; ignored otherwise. Install-specific; query the\ncost-code lookup rather than hardcoding."},"salesTax":{"type":"string","description":"``slstax``. **Not the sales-tax amount**: that is the header's\n`salesTax`. This is a free-form per-line tax code or flag, written verbatim.\nIt shares its name with the header field while being a different kind of\nvalue; the v2 spelling is kept rather than silently renamed."},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber","description":"The revenue GL account credited for this line. **Required when the\nheader's `status` is `1` (Open)**, the GL-posting status, and ignored\notherwise."},"subAccountNumber":{"type":"integer","format":"int64","default":0},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"``clnnum``. Written verbatim alongside the header's own `clientNumber`."}},"description":"One `the underlying record` line: a part, labor item, or credit against the work order."},"ServiceInventoryLineResult":{"type":"object","required":["id","recordNumber","lineNumber","partQuantity","partPrice","extendedQuantity","extendedPrice"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"lineNumber":{"type":"integer","format":"int32"},"description":{"type":"string"},"unitDescription":{"type":"string"},"partQuantity":{"$ref":"#/components/schemas/Decimal"},"partPrice":{"$ref":"#/components/schemas/Decimal"},"extendedQuantity":{"$ref":"#/components/schemas/Decimal"},"extendedPrice":{"$ref":"#/components/schemas/Decimal"},"accountNumber":{"$ref":"#/components/schemas/LedgerAccountNumber"},"subAccountNumber":{"type":"integer","format":"int64"},"clientNumber":{"$ref":"#/components/schemas/RecordNumber"}},"description":"A service inventory line as written."},"ServiceInventoryResult":{"type":"object","required":["id","recordNumber","orderDate","status","invoiceTotal","salesTax","invoiceType","priority","lines"],"properties":{"id":{"type":"string"},"recordNumber":{"$ref":"#/components/schemas/RecordNumber"},"orderNumber":{"type":"string"},"invoiceNumber":{"type":"string"},"clientNumber":{"$ref":"#/components/schemas/RecordNumber"},"orderDate":{"type":"string","format":"date"},"description":{"type":"string"},"status":{"$ref":"#/components/schemas/ServiceInventoryStatus"},"invoiceTotal":{"$ref":"#/components/schemas/Decimal"},"salesTax":{"$ref":"#/components/schemas/Decimal"},"taxDistrict":{"type":"integer","format":"int64"},"jobNumber":{"$ref":"#/components/schemas/RecordNumber"},"invoiceType":{"type":"integer","format":"int16"},"priority":{"type":"integer","format":"int16"},"scheduledDate":{"type":"string","format":"date"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ServiceInventoryLineResult"}}},"description":"A service inventory as written, header and lines."},"ServiceInventoryStatus":{"type":"integer","format":"int16","description":"``status`` (Sage 100 Contractor Service Receivables, menu 11-1).\n\n**Only `1` (Open) cascades to the general ledger.** Sage's own text is\n\"indicates you have invoiced the customer. Sage 100 Contractor posts the record\nto the general ledger.\" Every other value stores the header (`the underlying record`) and\nlines (`the underlying record`) only: no ledger transaction, no account/period/sub-account\nbalance propagation, and no job costs.\n\nThe values are deliberately **not** in workflow order: the lifecycle runs\n`6` (Quote) -> `7` (Work Order) -> (`8` Complete or `1` Open) -> `4` (Paid),\nso a `4`-Paid invoice is a settled document that has already posted and\nmust not post again.","x-enum-descriptions":["Open. Invoiced. Posts to the general ledger: account, period and sub-account balances propagate, and job costs are written when a job number is present.","Review. Stored only. No ledger transaction, no balance propagation, no job costs.","Dispute. Stored only.","Paid. A settled invoice that has already posted once (via a prior Open). Stored only; does not post again.","Void. Stored only.","Quote. Stored only. Earliest lifecycle state.","Work Order. Stored only. A scheduled service call, not yet an accounting document. The default when the caller omits status.","Complete. Stored only. The call has been performed but not yet invoiced.","Route. Stored only.","Contract. Stored only."],"x-enum-varnames":["Open","Review","Dispute","Paid","Void","Quote","WorkOrder","Complete","Route","Contract"],"enum":[1,2,3,4,5,6,7,8,9,10]},"ServiceInventoryWriteResult":{"type":"object","required":["document","jobCosts","inventory","dryRun"],"properties":{"document":{"$ref":"#/components/schemas/ServiceInventoryResult","description":"The document itself, with its lines."},"ledgerTransaction":{"$ref":"#/components/schemas/LedgerTransactionResult","description":"Absent when the document did not post to the general ledger."},"jobCosts":{"type":"array","items":{"$ref":"#/components/schemas/JobCostResult"},"description":"Always present, empty when none were written."},"inventory":{"type":"array","items":{"$ref":"#/components/schemas/InventoryMovementResult"},"description":"Always present, empty when nothing moved."},"dryRun":{"type":"boolean","description":"`true` when this response describes a rolled-back write rather than a\ncommitted one. See `dryRun` on the request.\n\nAlways present, so a body is self-describing on its own: a stored response, a\nlog line or a support ticket says which kind of write it was without needing\nthe request beside it. Declared here rather than on `WriteResult` so the\nservice-inventory envelope, which substitutes its own, cannot be the one that\nforgets to say."},"period":{"type":"integer","format":"int16","minimum":1,"maximum":13,"description":"The period the server resolved and posted into. **Absent on a store-only\nwrite**, which is any `status` other than `1` (Open), including the `7`\n(Work Order) default. Present whenever `ledgerTransaction` is."},"year":{"type":"integer","format":"int16","description":"The fiscal year the server resolved and posted into. Absent with `period`."}},"description":"The body of a successful `POST /service-inventories`.\n\nThe document, its posted journal (when `status` is `1`, Open), any job costs\nand the resolved period and year, each reported once.\n\n**This is the one write result whose `period` and `year` are optional.** A\nservice inventory only resolves a posting period when it posts, and the default\nstatus of `7` (Work Order) does not post, so store-only is the common path.\nWhen the two fields are present they mean what they mean on every other\nendpoint, and they are present exactly when `ledgerTransaction` is."},"ServiceLocation":{"type":"object","required":["id","clientNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"clientNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: the **client's** record number, a foreign key to ``recnum``,\nnot this row's identity.\n\n**Required, in the slot `recordNumber` occupies on every other read model.**\n`the underlying record` is keyed (`recnum`, `locnum`), so this column is not unique per row\nand the name `recordNumber` would be a false promise; it is published under\nthe name that describes what it actually holds."},"locationNumber":{"type":"integer","format":"int64","description":"`locnum`, the location's number **within the client**. Not a record number; ``locationNumber`` points here."},"locationName":{"type":"string","description":"`locnme`."},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string"},"zipCode":{"type":"string"},"phoneNumber":{"type":"string","description":"`phnnum`. Free text as Sage stores it; no format is imposed."},"contact":{"type":"string","description":"`contct`, the site contact's name, free text.\n\nBare `contact` here and `contact1` on `Client`, where the column is the first\nof three (`contc2`, `contc3`) and the suffix distinguishes them. One contact,\none name; `Vendor.contact` is spelled the same way."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a client's service location, as read.\n\n**This model has no `recordNumber`, and that is not an omission. It is what\nthe table is.** ``recnum`` holds the *client's* record number, not a row\nidentity, so it is published as `clientNumber`. A location is identified by the\npair (`recnum`, `locnum`): one client owns many locations, numbered within the\nclient. ``locnum`` is a foreign key to the second half of that pair, not\nto a record number.\n\n**So `clientNumber` is the required field here, and every other field is\noptional**. On every other read entity that slot is `recordNumber`. A caller\nkeying on `recordNumber` will not find one.\n\nNo change stamp: `the underlying record` carries none, so `lastUpdated` and `createdSince`\nboth answer 501."},"ServicePayment":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"description":{"type":"string","description":"`dscrpt`."},"checkNumber":{"type":"string","description":"`chknum`. Free text, not a number: Sage stores cheque and reference numbers as strings."},"checkDate":{"type":"string","format":"date","description":"`chkdte`. A date, not an instant; v2 returned `2026-01-15T00:00:00Z` for a Sage `Date` column."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, the payment applied. JSON string, native scale, as every decimal here is."},"discountTaken":{"$ref":"#/components/schemas/Decimal","description":"`dsctkn`, the discount taken on settlement.\n\nThe same column and the same spelling as `PayableInvoice.discountTaken`,\nwhich is the majority name; `discCredit` (the abbreviation two v2 models use\nand Sage does not) does not appear on this entity."},"appliedCredit":{"$ref":"#/components/schemas/Decimal","description":"`aplcrd`, credit applied to the settlement rather than paid."},"period":{"type":"integer","format":"int16","description":"`actper`, the period the payment posted into.\n\nSpelled `actper` here, as on `the underlying record` and `the underlying record`. Equipment cost and\nequipment revenue spell the same concept `actprd`; see `EquipmentCost.period`."},"year":{"type":"integer","format":"int16","description":"`postyr`."},"ledgerRecord":{"type":"integer","format":"int64","description":"`lgrrec`, the `recordNumber` of the `the underlying record` this payment posted, or `0` when\nno journal was written.\n\nNot typed `RecordNumber` (`@minValue(1)`) for that reason, and kept as\n`ledgerRecord` because all fourteen models carrying `lgrrec` agree on the\nspelling."}},"description":"`the underlying record`, a payment against a service receivable, as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\n**This entity carries no foreign key to the invoice it settles.** None of the\ncolumns exposed here is a client, an invoice, or a ``recnum``; the only\noutbound link is `lgrrec`, to the journal the payment posted. So a caller can\nlist payments and reconcile them against the general ledger, but **cannot join\na payment to the service invoice it paid** through this entity. Settle that\nbefore planning an AR aging report on it.\n\nNo change stamp: `the underlying record` carries none, so both `lastUpdated` and\n`createdSince` answer 501 here."},"ServiceSchedule":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`. See the model note: whether this is the row's own record number or a parent key is unverified."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"employeeNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`empnum`, the employee assigned, a foreign key to the employee master.\n\nPublished as `employeeNumber`, the single spelling this column carries across\nv3; `ServiceInventory` publishes its own `empnum` under the same name."},"equipmentNumber":{"type":"integer","format":"int64","description":"`eqpnum`. Not typed as a record number (`@minValue(1)` is not asserted): as\non `the underlying record`, an equipment number carried beside service records may be a\nper-client sequence rather than a record number, and which table this one\npoints at is unverified."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``: the subcontractor sent instead of an employee."},"priority":{"type":"integer","format":"int16","description":"`priort`.\n\n**The meaning of the codes is not established**: no Sage documentation for\nthis column has been found, so no members are published. Read the values off\nyour own install before relying on them."},"scheduleDate":{"type":"string","format":"date","description":"`schdte`, the date the call is booked for. A date, not an instant. `the underlying record` publishes its own `schdte` under this same name."},"estimatedHours":{"$ref":"#/components/schemas/Decimal","description":"`esthrs`. JSON string, native scale, as every decimal here is."},"completedDate":{"type":"string","format":"date","description":"`findte`, the finish date.\n\nThe Sage column says \"finish\" and the published name says \"completed\"; they\nare the same field. `completedDate` is this column's only spelling across the\nAPI."},"actualHours":{"$ref":"#/components/schemas/Decimal","description":"`acthrs`. The counterpart of `estimatedHours`; nothing reconciles the two."},"userDefined1":{"type":"string","description":"`usrdf1`."}},"description":"`the underlying record`, a scheduled service appointment, as read.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read may narrow the response with `select`.\n\n**What `recnum` points at is unverified.** It is published as `recordNumber`\nand modelled as one here, but it sits beside `linnum`, the same (parent key,\nline number) shape `the underlying record` and `the underlying record` use, where the `recnum` half is a\nforeign key to another table rather than a row identity. **Establish which it\nis against your install before keying on this field**; if it is a parent key,\nthe published name will have to change.\n\nNo change stamp: `the underlying record` carries none, so `lastUpdated` and `createdSince`\nboth answer 501. Note that neither would help much here: `schdte` is a\nbusiness date, chosen by whoever booked the call."},"ServiceType":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). The value ``invoiceType`` is expected to carry."},"typeName":{"type":"string","description":"`typnme`, the name a human reads. The whole reason to query this table."}},"description":"`the underlying record`, a service invoice type, as read.\n\nThree columns, and it is the most useful three-column table on the read\nsurface: **this is the lookup that makes ``invoiceType`` readable.**\nService invoice types are *not* a fixed enum: Sage defines them per company in\nmenu 11-1 and publishes only worked examples for a plumbing and an electrical\ncompany, so resolve a code through this table rather than hardcoding it.\n\n**The join to ``invoiceType`` is expected but unverified.** Confirm it\nagainst your install before relying on it as a contract.\n\nNo change stamp, as with every lookup table: `lastUpdated` and `createdSince`\nboth answer 501."},"SubChangeOrderLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"The parent row's UUID (`_idref`), spelled `idRef` in v2 as well.\n\nPresent only because this line is served as a top-level entity: it is the\nsole link back to the subcontract change order, and there is no parent entity\nto nest under."},"lineNumber":{"type":"integer","format":"int32"},"description":{"type":"string"},"budgetHours":{"$ref":"#/components/schemas/Decimal","description":"`chghrs`. Budgeted hours for the change, not a variance."},"budgetAmount":{"$ref":"#/components/schemas/Decimal","description":"`bdgprc`, the budget in total.\n\nThe same Sage column is published as `budgetPerUnit` on a units-complete\nline, where it is a per-unit figure. Here it is the total."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"Sage's vendor record number."},"vendorContract":{"type":"integer","format":"int64","description":"`vndctc`. A vendor contract reference; the table it points at is not exposed by this API."},"changeNumber":{"type":"string","description":"The change order number this line belongs to, as a string, as Sage stores it."},"changeStatus":{"type":"integer","format":"int16","description":"`chgsts`, the change order line's status.\n\n**Published as an integer with no member list**, because the value set for\nthis column is not verified. Do not assume it matches any other status family\nin this API."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`. Install-specific, so not an enum; query the cost-code lookup (`/query/cost-code`)."},"costType":{"type":"integer","format":"int16","description":"`csttyp`. Sage presets `1`-`5`; `6`-`9` are per company. Query `/query/cost-type`. See `ChangeOrderLine.costType`."},"userDefined1":{"type":"string"}},"description":"`the underlying record`, a subcontract change order line, as read.\n\n**Structurally anomalous.** This is a line table served as a top-level entity\n(`/query/sub-change-order-line`), and it is the child of a parent that has no\nroute of its own, so `idRef` points at a row this API cannot currently return.\nThere is no way to fetch the subcontract change order these lines belong to,\nand that is a coverage gap rather than a fault in a request.\n\n`id` and `recordNumber` are always present; every other field is optional\nbecause a v3 query can restrict the response with `select`."},"Subcontract":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"contractNumber":{"type":"string","description":"`ctcnum`, Sage's \"Subcontract#\": a caller-facing document number, a string,\nnot an integer. Sage generates the next one on entry."},"divisionNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`divnum`, a foreign key to ``recnum`` (Cost Divisions). Query the cost-division lookup."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, a foreign key to ``phsnum``."},"description":{"type":"string","description":"`dscrpt`."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``. Install-specific rather than a\nfixed set; query the tax-district lookup."},"status":{"type":"integer","format":"int16","description":"`status`, Sage `Numeric(1)`.\n\n**Deliberately published as an integer, with no member list**, because the\nvalue set for this column is not verified. Do **not** assume the\n`1-Open` / `2-Review` / `3-Dispute` sequence that several other Sage families\nbegin with: Sage's own data dictionary gives this column a default of `4`,\nwhich no member of that sequence would explain. Resolve it against Sage's\nhelp before branching on it."},"contractType":{"type":"integer","format":"int16","description":"`contyp`, a foreign key to `SBCTYP.recnum` (Subcontract Types).\n\nInstall-specific, so not an enum. **There is no lookup endpoint for `SBCTYP`\ntoday**, the same gap as a purchase order's `orderType`, so a caller cannot\ncurrently resolve this code through the API."},"retentionRate":{"$ref":"#/components/schemas/Decimal","description":"`rtnrte`, Sage `Numeric(2,2)`. A retention rate, not an amount."},"contractDate":{"type":"string","format":"date","description":"`condte`, Sage's \"Subcontract Date\". A date, not an instant."},"hotList":{"type":"integer","format":"int16","description":"`hotlst`, Sage `Numeric(1)`, dictionary default `1`. Sage's \"Hot List\" flag.\nPublished as the integer it is rather than guessed at as a boolean or an\nenum, because the value set is not verified."},"enteredDate":{"type":"string","format":"date","description":"`entdte`."},"userName":{"type":"string","description":"`usrnme`."},"originalTotal":{"$ref":"#/components/schemas/Decimal","description":"`cntttl`, Sage's \"Original Subcontract\"."},"changesTotal":{"$ref":"#/components/schemas/Decimal","description":"`chgttl`, Sage's \"Changes\"."},"newContractTotal":{"$ref":"#/components/schemas/Decimal","description":"`ctcttl`, Sage's \"New Subcontract\". The committed total; the header analogue of a line's `contractAmount`."},"salesTaxTotal":{"$ref":"#/components/schemas/Decimal","description":"`taxttl`."},"invoicedTotal":{"$ref":"#/components/schemas/Decimal","description":"`invttl`, Sage's \"Invoiced\"."},"balanceTotal":{"$ref":"#/components/schemas/Decimal","description":"`balttl`, Sage's \"Remaining Total\". The same Sage column is published as\n`balanceTotal` on a progress billing."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/SubcontractLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`."}},"description":"`the underlying record`, a subcontract, as read.\n\n**Every field except `id` and `recordNumber` is optional**, because a v3 query\nmay restrict the response with `select`. Optionality here describes what the\ncaller asked for, not what Sage stores.\n\n`lines` has always existed at runtime; nothing in the published v2 spec\ndescribed it.\n\nSage carries columns on `the underlying record` that this API does not read at all: the bid and\nperformance bonds (`pmtbnd`, `prfbnd`), the approval date (`appdte`), the four\nschedule dates (`orgstr`, `orgfin`, `strdte`, `findte`), substantial completion\n(`subcmp`), the pay hold (`paysts`) and the GST/PST/HST amounts. They are out\nof scope for a spec that describes what the API returns, and worth knowing\nabout before anyone concludes the API cannot answer a question about them."},"SubcontractLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`)."},"idRef":{"type":"string","description":"The parent subcontract's `id` (`_idref`), the column the child fetch joins\non. Redundant when read nested under the parent, which is the only way it can\nbe read, but the column is in the child `SELECT` and therefore on the wire."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`."},"description":{"type":"string","description":"`dscrpt`. Sage defaults it from the parent's description."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"costType":{"type":"integer","format":"int16","description":"`csttyp`, a foreign key to ``recnum``.\n\n`1` Material, `2` Labor, `3` Equipment, `4` Subcontract and `5` Other are\nSage presets; **`6` to `9` are defined per company**, so this is deliberately\nnot an enum. Query the cost-type lookup. Note `4` is itself \"Subcontract\",\nwhich is a cost classification and unrelated to this table."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, Sage's \"Original Subcontract\". The line before change orders."},"change":{"$ref":"#/components/schemas/Decimal","description":"`change`, Sage's \"Changes\". The change-order movement on this line."},"contractAmount":{"$ref":"#/components/schemas/Decimal","description":"`cntrct`, Sage's \"New Subcontract\": the committed amount on this line, after\nchange orders.\n\n**`contract` in v2.** Renamed for the suffix that marks it as money, and to\nmatch the name the same Sage column already carries on the job (`the underlying record`).\nNote this is *not* a budget: a subcontract line is a commitment to a vendor,\nand `budgetAmount` on other entities means something else."},"billed":{"$ref":"#/components/schemas/Decimal","description":"`billed`, Sage's \"Invoiced\". The header calls the same concept\n`invoicedTotal` (`invttl`), so the entity uses two words for one idea."},"remaining":{"$ref":"#/components/schemas/Decimal","description":"`remain`, Sage's \"Remaining\". The header's counterpart is `balanceTotal`."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a subcontract line, as read.\n\n**This schema has never been published**, so the field names below were\nundiscoverable to a v2 reader.\n\nThe five money columns are Sage's subcontract arithmetic, one line at a time,\nand they mirror the header totals column for column:\n\n| Line | Sage label | Header |\n|---|---|---|\n| `amount` | Original Subcontract | `originalTotal` |\n| `change` | Changes | `changesTotal` |\n| `contractAmount` | New Subcontract | `newContractTotal` |\n| `billed` | Invoiced | `invoicedTotal` |\n| `remaining` | Remaining | `balanceTotal` |"},"TaxDistrict":{"type":"object","required":["recordNumber"],"properties":{"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"District#\", `Numeric(6)`.\n\n**This is the value every `taxDistrict` field on every other entity carries**:\n`Vendor.taxDistrict`, `Subcontract.taxDistrict`, `PurchaseOrder.taxDistrict`\nand the rest, all typed `int32` to match `Numeric(6)`."},"districtName":{"type":"string","description":"`dstnme`. The name a human configured in 3-5; install-specific."},"taxEntity1":{"type":"integer","format":"int32","description":"`entty1`, a foreign key to ``recnum``: the first taxing entity in the\ndistrict.\n\n**Five numbered columns, not a child collection.** Sage caps a district at\nfive entities and stores them in fixed slots, so there is no `children` list\nto request here and a caller reads all five. `0` means the slot is unused,\nwhich is the ordinary case; most districts use one or two."},"taxEntity2":{"type":"integer","format":"int32","description":"`entty2`, a foreign key to ``recnum``. See `taxEntity1`."},"taxEntity3":{"type":"integer","format":"int32","description":"`entty3`, a foreign key to ``recnum``. See `taxEntity1`."},"taxEntity4":{"type":"integer","format":"int32","description":"`entty4`, a foreign key to ``recnum``. See `taxEntity1`."},"taxEntity5":{"type":"integer","format":"int32","description":"`entty5`, a foreign key to ``recnum``. See `taxEntity1`."},"totalTaxRate":{"$ref":"#/components/schemas/Decimal","description":"`sumrte`, Sage `Numeric(3,4)`, \"Total Tax Rate\". JSON string, native scale.\n\nThe district's combined rate: the sum of its entities' `taxRate` values. A\nrate, not an amount, and stored by Sage rather than derived on read, so a\ncaller that finds the two disagreeing is looking at Sage's state."},"materialTax":{"type":"integer","format":"int16","description":"`mattax`, Sage's \"Material\": does this district tax material?\n\n**The six flags below are per cost type, and four cost types are missing.**\n`materialTax`, `laborTax`, `equipmentTax`, `subcontractTax` and `otherTax` are\ncost types 1 to 5, Sage's presets; `profitTax` is the sixth flag. Sage also\nstores taxable flags for the four **install-defined** cost types, 6 to 9, and\nthis entity does not publish them. So on an install that uses a\ncompany-defined cost type, this entity cannot say whether the district taxes\nit. Worth knowing before treating the six as complete.\n\nEach is a `Yes/No` column in Sage's dictionary arriving as `0` or `1`, and is\ndeclared as an integer rather than a boolean."},"laborTax":{"type":"integer","format":"int16","description":"`labtax`, cost type 2. A `Yes/No` column arriving as `0`/`1`; see `materialTax`."},"equipmentTax":{"type":"integer","format":"int16","description":"`eqptax`, cost type 3. See `materialTax`."},"subcontractTax":{"type":"integer","format":"int16","description":"`subtax`, cost type 4. See `materialTax`.\n\nCost type 4 is itself called \"Subcontract\"; this is a tax flag on that cost\nclassification and has nothing to do with the `the underlying record` table."},"otherTax":{"type":"integer","format":"int16","description":"`othtax`, cost type 5. See `materialTax`."},"profitTax":{"type":"integer","format":"int16","description":"`pfttax`, Sage's \"Profit\". Not a cost type: the flag for tax on the profit component. See `materialTax`."}},"description":"`the underlying record`, Sage's \"Tax Districts\", as read.\n\nA sales-tax district: a named bundle of up to five taxing entities, a combined\nrate, and which cost types the district taxes.\n\n**This is the lookup behind every `taxDistrict` field on this surface.** That\nforeign key is carried by client, vendor, progress billing, purchase order,\nreceivable invoice, service inventory and subcontract, and it is never\nvalidated on write, so a caller that posts a district number should read the\ndistricts first and post one that is in the list.\n\n**This table has no `_idnum`, so there is no `id`** and `recordNumber` is the\nonly required field. Paging stays total because `recnum` is present and is used\nas the sort tiebreak in its place."},"TaxEntity":{"type":"object","required":["recordNumber"],"properties":{"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Tax Entity#\", `Numeric(6)`.\n\nThe value `TaxDistrict.taxEntity1` through `taxEntity5` carry."},"entityName":{"type":"string","description":"`entnme`. The authority's name, as configured in 3-5. Install-specific."},"remitTo":{"type":"string","description":"`paynme`, Sage's \"Remit To\": who the cheque for this tax is made out to, when\nthat differs from the entity's own name.\n\nThe same Sage column is published as `payeeName` on `the underlying record`; the two names\ndiffer because the roles do."},"addressLine1":{"type":"string","description":"`addrs1`."},"addressLine2":{"type":"string","description":"`addrs2`."},"cityName":{"type":"string","description":"`ctynme`."},"stateCode":{"type":"string","description":"`state_`. Two-letter state or province code; the underscore is Sage escaping a reserved word."},"zipCode":{"type":"string","description":"`zipcde`."},"taxRate":{"$ref":"#/components/schemas/Decimal","description":"`taxrt1`, Sage `Numeric(3,4)`, \"Tax Rate\". JSON string, native scale.\n\nA rate, not an amount. `TaxDistrict.totalTaxRate` is the sum of these across\nthe district's entities, at the same scale.\n\n**The `1` in the column name is Sage's and there is no second slot**, which\nis why the published field name drops the digit."},"billingLimit":{"$ref":"#/components/schemas/Decimal","description":"`limit1`, Sage `Numeric(7,4)`, \"Billing Limit\": the threshold at which this\nentity's rate stops applying.\n\nA cap, and its unit depends on `limitType`, which is undocumented, so this\nnumber cannot be interpreted on its own."},"limitType":{"type":"integer","format":"int16","description":"`lmtyp1`, Sage `Numeric(1)`, \"Limit Type\": what `billingLimit` is measured\nagainst.\n\n**Deliberately not an enum**: Sage's published help does not describe the\nvalues, so read the meanings off the install before using `billingLimit` for\nanything."}},"description":"`the underlying record`, Sage's \"Tax Entities\", as read.\n\nA single taxing authority (a state, a county, a city) with its rate and the\naddress the tax is remitted to. `TaxDistrict` bundles up to five of these and\npublishes their combined rate; this is where the individual rates and the\nremit-to details live, and it is the only place a caller can see *why* a\ndistrict's total is what it is.\n\n**This table has no `_idnum`, so there is no `id`** and `recordNumber` is the\nonly required field. Paging stays total because `recnum` is present and is used\nas the sort tiebreak in its place.\n\n`ntetxt` exists on the table and this API does not select it."},"TimecardCalculation":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the parent check's record number, not this row's.** A foreign\nkey to ``recnum``, the `PayrollRecord` entity's `recordNumber`, and the\nfilter that fetches a check's withholding detail. See\n`TimecardLine.recordNumber` for why the name is kept."},"idRef":{"type":"string","description":"The parent check's `id` (`_idref`), the UUID side of the same link."},"calculationNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`clcnum`, a foreign key to ``recnum``: the `PayrollCalculation`\nentity's `recordNumber`. It says **which** tax or deduction this amount is,\nand it is the only thing that does: this table carries no code or name of its\nown.\n\n**Named `calculation` in v2, published as `calculationNumber` here**, the\nsame spelling `EmployeeEarnings` now uses for the same target table. It is a\nforeign key and **not an enum**, however few values an install has."},"amount":{"$ref":"#/components/schemas/Decimal","description":"`amount`, the amount withheld or accrued for this calculation on this check."},"override":{"type":"integer","format":"int16","description":"`ovrrid`: \"this amount was overridden rather than computed by Sage's\ncalculation engine\", which is what every OPDC-posted amount is, since the\nprovider computed it.\n\n**Treat the encoding of this column as unconfirmed.** It is published as an\ninteger and written as `1`, but Sage 20.1 declares it `nvarchar(1)` and no\nobserved data establishes whether its domain is `'1'`/`'0'` or `'Y'`/`'N'`.\nDo not branch on the value without checking what your own install stores."},"stateWages":{"$ref":"#/components/schemas/Decimal","description":"`stewge`, state-taxable wages for this calculation. Sage-maintained; OPDC never writes it."},"stateGross":{"$ref":"#/components/schemas/Decimal","description":"`stegrs`, state gross wages. Also Sage-maintained."},"ytdAmount":{"$ref":"#/components/schemas/Decimal","description":"`ytdamt`, the year-to-date amount for this calculation.\n\n**Not maintained by OPDC.** A payroll post writes this row's `amount` and\noverride flag only, so on a company posting through OPDC this column\nreflects only natively-run payroll. Do not file against it."}},"description":"`the underlying record`, a timecard calculation: **one row per tax or deduction on a\npaycheck**, as read.\n\nThe per-calculation withholding detail behind `PayrollRecord.deductionsTotal`:\nwhat Sage's Compute step writes for a natively-run payroll, and what a payroll\npost writes from the provider's already-computed amounts.\n\nLike `TimecardLine` and `PayrollPosting`, this is a detail table read as a\ntop-level entity because `the underlying record` declares no child relation; see\n`TimecardLine`'s model note.\n\n**A deduction with no calculation number would post to the GL and never appear\nhere**, which is why a batch containing one is rejected rather than partially\nposted: dropping it would leave the ledger holding a credit the subledger\ncannot explain. So the sum of `amount` over a check's rows equals its\n`deductionsTotal`, and that is an invariant a caller can rely on for anything\nOPDC posted.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"TimecardLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`: **the parent check's record number, not this line's.**\n\nA foreign key to ``recnum``, the `PayrollRecord` entity's\n`recordNumber`; the parent's value is written straight into it. Filter this\nentity on `recordNumber` to fetch one check's earnings; that is exactly how\nit is meant to be read.\n\nThe field keeps the name `recordNumber` because that is the column's\npublished name and every other line table in the product spells it the same\nway. It is not unique on this entity: a line has no record number of its\nown, only an `id` and a `lineNumber`."},"idRef":{"type":"string","description":"The parent check's `id` (`_idref`), the UUID side of the same link."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`, the line's position on the check."},"dateWorked":{"type":"string","format":"date","description":"`dtewrk`, the date worked. A date, not an instant.\n\n**On an OPDC-posted check this is the pay-period end, not the day worked.**\nA payroll post has no per-day detail to write, since the provider has already\nsummarised the period into earnings, so every line carries the batch's\n`periodEnd`. Day-level hours live in `DailyPayroll`, which OPDC does not\nwrite. Rows entered natively in Sage carry the real date."},"description":{"type":"string","description":"`dscrpt`, the earning code: `REG`, `OT`, `PTO` and the like, as the provider\nspells them. Free text, **truncated to 50 characters** on the way in."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``: the `Job` entity. `the underlying record` is\nthe job, not the job cost.\n\nThe link that routes wages to job costing. Note that **a payroll post writes\nno `the underlying record` row of its own**: it carries the job on the timecard line as\nmetadata and leaves job costing to Sage."},"phaseNumber":{"type":"integer","format":"int64","description":"`phsnum`, a foreign key to ``phsnum``. Not a `recnum`, hence a plain `int64`."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\nInstall-specific and **deliberately not an enum**: query the cost-code\nlookup instead."},"payType":{"type":"integer","format":"int16","description":"`paytyp`, the earning pay type: `1-Regular`, `2-Overtime`, `3-Premium`,\n`4-Sick`, `5-Vacation`, `6-Holiday`, `7-Piece`, `8-Per Diem`,\n`9-Miscellaneous`, verified against Sage's own help. It is the value a\npayroll post buckets the parent's hours and pay columns by, so a wrong value\nhere desynchronises `PayrollRecord` from the journal.\n\n**Not the same field as `PayrollRecord.payrollType`**, which is the same\ncolumn name on the header holding the check's own type code.\n\nIt is typed as an integer rather than an enum because the shared enum the\nread entities would draw this family from does not exist yet."},"paygroup":{"$ref":"#/components/schemas/RecordNumber","description":"`paygrp`, a foreign key to ``recnum``. See `Employee.paygroup` for why the name carries no `Number` suffix."},"payRate":{"$ref":"#/components/schemas/Decimal","description":"`payrte`, the hourly rate for this earning.\n\n**`0` for pay type `7-Piece`**, whose rate is the per-piece rate in `pcerte`,\na column this model does not publish."},"hoursWorked":{"$ref":"#/components/schemas/Decimal","description":"`hrswrk`, the hours. **`0` for `7-Piece`**, whose quantity is a piece count in\nthe unpublished `pieces` column, and `0` for `8-Per Diem` and\n`9-Miscellaneous`, which are lump sums carrying no quantity at all."},"compCode":{"$ref":"#/components/schemas/RecordNumber","description":"`cmpcde`, a foreign key to ``recnum``: the `WorkersComp` entity. ``wrkcmp`` is the employee-level default."},"department":{"type":"integer","format":"int64","description":"`dptmnt`. A record number into the department table; `0` when unset. This is\nhalf the key `PayrollPosting` groups its GL debit legs by."},"userDefined1":{"type":"string","description":"`usrdf1`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a timecard line: **one row per earning on a paycheck**, as read.\n\nThe detail behind `PayrollRecord`'s pay buckets: hours, rate and pay type, plus\nthe job / phase / cost-code routing that is what carries wages into job\ncosting. A payroll post writes one of these per earning in the batch.\n\n**This is a line table read as a top-level entity, not as a child.** Its\nparent, `PayrollRecord`, declares no child relation, so there is no nesting\nmechanism to return it under; a caller fetches a check's earnings by querying\nthis entity filtered on `recordNumber`, below, and gets a complete answer.\n\n**Sage columns this model does not read**, all three present in the database\non a check OPDC posted: `jobcst`, **the line's dollar amount**, and the\npiece-work pair `pieces` / `pcerte`. The consequence is concrete: this entity\nreturns an earning's hours and rate but **not its amount**, so a caller that\nneeds the money must multiply, and cannot do even that for pay type\n`7-Piece`, whose quantity is a piece count in a column that is not published\nand whose rate is not in `payRate`.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."},"TransactionSource":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, which Sage labels \"Source\", `Numeric(2)`. The value `sourceNumber` carries on `the underlying record`, `the underlying record` and `the underlying record`."},"sourceName":{"type":"string","description":"`srcnme`, Sage `Character(20)`: the short name, e.g. `GL Check`, `AP Invoice`, `PO Receipt`. Sage's longer `srcdsc` is not returned; see the model note."}},"description":"`the underlying record`, the transaction-source lookup (1-3).\n\n**This is the table `LedgerTransaction.sourceNumber`, `JobCost.sourceNumber`\nand ``srcnum`` all point at.** One row per Sage menu that can originate a\nposting, which is what makes a journal traceable to the document that produced\nit.\n\n**Sources `1`-`33` are system-defined; `34` and above are install-specific.**\nSage publishes no enumeration, but the table is self-documenting: every row\nnames the Sage menu it corresponds to. `recnum` is `Numeric(2)`, so the range\ntops out at 99. Resolve codes here rather than hardcoding them.\n\n**Nothing validates a caller-supplied `sourceNumber` against this table**, so\nan unknown source will be stored as given.\n\nSage carries a longer description column (`srcdsc`) alongside `srcnme`; this\nAPI does not return it, so a caller gets the short name only."},"UnitsComplete":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`), which for this entity is the job number."},"editDate":{"type":"string","format":"date","description":"When the projection was last edited (`edtdte`).\n\nA date, not an instant. This is the column `lastUpdated` filters on for this\nentity; see the model note."},"userName":{"type":"string","description":"The Sage user who last edited the projection (`usrnme`)."},"noteText":{"type":"string","description":"Record note (`ntetxt`)."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/UnitsCompleteLine"},"description":"Child collection `lines` (`the underlying record`), requested via `children: [\"lines\"]`.\n\nChildren ignore `select` and are unpaginated and unfilterable."}},"description":"`the underlying record`, a job's units-complete projection, as read.\n\nOne per job: Sage keys `the underlying record` on `recnum` and the table has no job column to\nkey it any other way: the record number *is* the job number (``recnum``),\nas on `the underlying record`, `the underlying record` and `the underlying record`. The header is the same four columns those\nthree carry; everything specific to this entity is on the line.\n\n**The route is `/units-completes`.** The plural is mechanical rather than\ngrammatical, so that the rule needs no looking up.\n\n**One of only seven entities where `lastUpdated` is honoured rather than\nanswered with a 501**: ``edtdte`` is a real edit stamp, alongside\n`the underlying record`, `the underlying record`, `the underlying record`, `the underlying record`, `the underlying record` and `the underlying record`.\n\nOnly `id` and `recordNumber` are required. Every other field is optional because\na v3 read may restrict the response with `select`, so an absent field means\n\"not asked for\", not \"not set\"."},"UnitsCompleteLine":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The parent record's record number (`recnum`), which is the job number.\nRepeated on every line; see the note above."},"idRef":{"type":"string","description":"`_idref`, the `id` of the owning `UnitsComplete`, and the column the child\nfetch joins on. On the wire even when nested under the parent it names: the\nchild `SELECT` is built from the full column map and ignores `select`."},"phaseNumber":{"type":"integer","format":"int64","description":"The job phase (`phsnum`), a foreign key to the job-phase lookup."},"lineNumber":{"type":"integer","format":"int32","description":"Position within the phase (`linnum`)."},"costCode":{"$ref":"#/components/schemas/Decimal","description":"`cstcde`, Sage `Numeric(11,3)`, a foreign key to ``recnum``.\n\nInstall-specific, so not an enum. Query the cost-code lookup."},"budgetCost":{"$ref":"#/components/schemas/Decimal","description":"The budgeted cost for this cost code (`bdgcst`), in money. The counterpart of ``totalBudget``."},"budgetUnits":{"$ref":"#/components/schemas/Decimal","description":"The budgeted quantity for this cost code (`bdgunt`), in units."},"costToDate":{"$ref":"#/components/schemas/Decimal","description":"Cost posted against this cost code so far (`cstdte`). Same column name and same meaning as on `the underlying record`."},"unitsCompleted":{"$ref":"#/components/schemas/Decimal","description":"Units installed so far (`untcmp`): the progress figure this table exists to record."},"budgetPerUnit":{"$ref":"#/components/schemas/Decimal","description":"The budgeted cost of one unit (`bdgprc`), that is `budgetCost` over\n`budgetUnits`.\n\nThe same Sage column is published as `budgetAmount` on a subcontract change\norder line, where it is a total rather than a per-unit figure."},"actualPerUnit":{"$ref":"#/components/schemas/Decimal","description":"The actual cost of one unit so far (`actprc`), that is `costToDate` over\n`unitsCompleted`. The figure `budgetPerUnit` is judged against."},"overUnderUnit":{"$ref":"#/components/schemas/Decimal","description":"Variance per unit (`ovrund`): `actualPerUnit` against `budgetPerUnit`.\n\n**Qualified `overUnderUnit` where `the underlying record` and `the underlying record` publish the same\ncolumn as plain `overUnder`, and that is not drift.** This table is the only\none of the three carrying two variances, so the plain name would not say\nwhich. Compare `overUnderTotal` below."},"unitsRemaining":{"$ref":"#/components/schemas/Decimal","description":"Units still to install (`untrem`), that is `budgetUnits` less `unitsCompleted`."},"costToComplete":{"$ref":"#/components/schemas/Decimal","description":"The cost still to come (`cstcmp`), Sage's projection for the remaining units.\n\nThe same column name and the same published name as ``costToComplete``,\nreached a different way: there it is driven off the entered `actualComplete`\npercentage, here off `unitsRemaining` at `actualPerUnit`."},"overUnderTotal":{"$ref":"#/components/schemas/Decimal","description":"Variance in total (`ovrttl`): projected cost against `budgetCost` for the whole cost code."},"userDefined1":{"type":"string","description":"Free text, per install (`usrdf1`)."},"noteText":{"type":"string","description":"Line note (`ntetxt`)."}},"description":"`the underlying record`, a units-complete line, as read.\n\nOne row per phase + cost code. The same skeleton as `the underlying record` and `the underlying record` (a\nbudget, a to-date figure, progress, a remainder and a variance), but measured\ntwo ways at once, which is what makes this the widest of the three: it carries a\nbudget in **cost** (`bdgcst`) and a budget in **units** (`bdgunt`), and a\nvariance per unit (`ovrund`) as well as in total (`ovrttl`).\n\nAs on `the underlying record`, `the underlying record` and `the underlying record`, `recordNumber` is **not** unique to a\nline: Sage keys `the underlying record` on the parent's `recnum`, which is the job number, so\nline identity is `recordNumber` + `phaseNumber` + `lineNumber`.\n\nEvery decimal below is a JSON **string** at native scale. The v2 spec declared\nthem `\"type\": \"number\"` while the wire carried a string."},"UnlockRequest":{"type":"object","required":["userName"],"properties":{"userName":{"type":"string","description":"The user releasing the lock. Required."},"override":{"type":"boolean","description":"Release a lock held by a **different** user.\n\nWithout this, unlocking someone else's lock is a `409`. Use it when the\nholder cannot release it themselves: an interrupted maintenance window\nleaves the lock in place, and it does not time out on its own.","default":false}},"description":"Who is releasing the lock, and whether to force it."},"UtilityProblem":{"type":"object","required":["type","title","status"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string"},"correlationId":{"type":"string"}},"description":"The problem body emitted by v3 utility handlers. It mirrors `Problem`."},"Vendor":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`).\n\nThis is the value every other entity carries as `vendorNumber`. v2 spelled\nthat foreign key `vendorRecordNumber` on purchase order alone; v3 uses\n`vendorNumber` everywhere."},"vendorName":{"type":"string"},"shortName":{"type":"string"},"ownerName":{"type":"string"},"contact":{"type":"string"},"addressLine1":{"type":"string"},"addressLine2":{"type":"string"},"cityName":{"type":"string"},"stateCode":{"type":"string","description":"`state_`. Two-letter state or province code, as entered."},"zipCode":{"type":"string"},"phoneNumber":{"type":"string"},"faxNumber":{"type":"string"},"cellPhone":{"type":"string"},"email":{"type":"string"},"federalIdNumber":{"type":"string"},"stateIdNumber":{"type":"string"},"licenseNumber":{"type":"string"},"accountNumber":{"type":"string","description":"The account number this company holds **with** the vendor, free text.\n\nNot a ledger account. Sage stores it in `actnum`, which is also the column\nthe underlying record uses for a GL account number, and v2 published both as\n`accountNumber`: one field name over two unrelated types. The invoice line\nis renamed `ledgerAccount` in v3; this one keeps the name, because here it\nreally is an account number at the vendor."},"userDefined1":{"type":"string","description":"`usrdf1`.\n\n**Spelled `userDefinedField1` on this model alone in v2.** Every other model\nthat exposes the column calls it `userDefined1`; the underlying record was the outlier, and\nv3 brings it into line."},"userDefined2":{"type":"string","description":"`usrdf2`. Spelled `userDefinedField2` in v2; see `userDefined1`."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"JSON string, native scale. Declared `number` in the v2 spec while the wire carried a string."},"discountDateTerms":{"type":"string","description":"Payment terms text, **not a date**, despite the column being `dscdte`.\n\nSage reuses `dscdte`/`duedte` for a date on invoices and for a terms string\non the vendor and job masters, which is why this is a `string` here and a\n`plainDate` on `PayableInvoice.discountDate`."},"dueDateTerms":{"type":"string","description":"Payment terms text, not a date (`duedte`). See `discountDateTerms`."},"ledgerDefault":{"type":"integer","format":"int64","description":"The vendor's default GL account (`lgrdft`), carried onto a new payable\ninvoice by Sage.\n\nA vendor with no default reads back `0` rather than being omitted, so this\nfield is a plain integer and is not constrained to a valid account number."},"creditDefault":{"$ref":"#/components/schemas/Decimal","description":"`cdedft`. Meaning not established beyond Sage's column name; no documented value set."},"typeDefault":{"type":"integer","format":"int16","description":"Defaults Sage carries onto a new record for this vendor (`typdft`,\n`stsdft`, `wrndft`).\n\nLeft as plain integers deliberately. `typdft` and `stsdft` resemble the\n`ApInvoiceType` and `ApInvoiceStatus` families, but Sage's published help\ndoes not confirm that the underlying record draws from them, so they are not typed as those\nenums. Read the meanings off the install."},"statusDefault":{"type":"integer","format":"int16","description":"See `typeDefault`. Not typed as `ApInvoiceStatus`; the link is unverified."},"warrantyDefault":{"type":"integer","format":"int16","description":"See `typeDefault`. `wrndft`; no documented value set."},"beginningBalance":{"$ref":"#/components/schemas/Decimal"},"endingBalance":{"$ref":"#/components/schemas/Decimal","description":"`endbal`. the underlying record and the underlying record publish the same column as `endBalance`; the underlying record's spelling is the majority one."},"vendorType":{"type":"integer","format":"int16","description":"`vndtyp`, a foreign key to ``recnum``. Install-configurable; no enum. Read the labels from the vendor-type table."},"minorityType":{"type":"integer","format":"int16","description":"`minsts`, minority status. Coded; no Sage source found. No enum."},"taxDistrict":{"type":"integer","format":"int32","description":"`taxdst`, a foreign key to ``recnum``: Sage's tax district table.\n**Not an enum**; districts are configured per install."},"compRate":{"$ref":"#/components/schemas/Decimal","description":"`cmprte`. A rate, not an amount."},"useTaxRate":{"$ref":"#/components/schemas/Decimal","description":"`utxrte`. A rate, not an amount, unlike `PayableInvoice.useTaxRate`, whose column is `usetax`."},"orderType":{"type":"integer","format":"int16","description":"`ordtyp`. Coded; no Sage source found. No enum."},"orderDescription":{"type":"string"},"subcontractType":{"type":"integer","format":"int16","description":"`contyp`, the vendor's default subcontract type.\n\nThe same column is published as `contractType` on the subcontract itself,\nwhich is different data rather than an inconsistency. Coded; no Sage source\nfound."},"accountType":{"type":"integer","format":"int16","description":"`acttyp`. Coded; no Sage source found. No enum."},"print1099":{"type":"integer","format":"int16","description":"Flags Sage stores as integers, not booleans, and which arrive on the wire as\nintegers: `prt199` (issue a 1099), `intrnl` (an internal vendor),\n`dirdep` (ACH / direct deposit is set up), `inactv` (the vendor is retired).\n\nDo not assume the values are limited to `0` and `1`: `print1099` in\nparticular is a 1099 **box** code in some Sage versions."},"internalVendor":{"type":"integer","format":"int16","description":"See `print1099`. `intrnl`."},"achEnabled":{"type":"integer","format":"int16","description":"See `print1099`. The column is `dirdep`, direct deposit; v2 exposes it as `achEnabled`."},"isInactive":{"type":"integer","format":"int16","description":"See `print1099`. `inactv`. the underlying record and the underlying record publish the same column as `inactive`."},"returnNumber":{"type":"string","description":"`rtnmbr`. A free-text reference, not a Sage record number."},"bankAccount":{"type":"string"},"achEmail":{"type":"string","description":"`acheml`. Where remittance advice for an ACH payment is sent."},"noteText":{"type":"string","description":"`ntetxt`. Most entities publish this column as `noteText`; the underlying record and the underlying record say `notes`."}},"description":"`the underlying record`, the vendor master, as read.\n\nSage names the table for the module rather than for the record: the underlying record is\nAccounts Payable's vendor list, not a payment.\n\n**Only `id` and `recordNumber` are always present.** Every other field is\noptional because a read request may narrow the response with `select`, in\nwhich case a column that was not asked for is absent from the row entirely.\nA column that is NULL in Sage comes back as JSON `null`.\n\nthe underlying record has no child collection and no modification stamp, so there is no\n`lastUpdated` and no incremental read for vendors."},"VendorActivity":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`).\n\nThe only unique column on the row. `vendorNumber` repeats across years and\n`fiscalYear` repeats across vendors; only the pair identifies a row, and only\n`id` does so in one field."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``: the vendor master, published as the `Vendor` entity."},"fiscalYear":{"type":"integer","format":"int16","description":"`fscyer`, Sage `Numeric(4)`: the four-digit fiscal year the activity is\naccumulated into.\n\nA **fiscal** year, so it is the year of the company's ledger calendar rather\nthan the calendar year, and it does not line up with the calendar year on an\ninstall whose fiscal year does not start in January."},"invoiceActivity":{"$ref":"#/components/schemas/Decimal","description":"`ytdact`, Sage `Numeric(12,2)`, labelled \"Invoice Activity\". JSON string,\nnative scale.\n\n**Invoiced, not paid.** The paid figure lives on the invoice\n(``ttlpad``), and nothing in this table records payment, so this cannot\nbe used as a 1099 cash-basis total on its own."}},"description":"`the underlying record`, Sage's \"Vendor Invoice Activity\", as read.\n\nOne row per vendor per fiscal year, holding what that vendor was invoiced in\nthat year. It is the table behind a 1099 total and behind \"what did we spend\nwith this vendor last year\", and it is the only place either question is\nanswered without summing `the underlying record`.\n\n**No `recnum` here either**, for the same reason as `VendorBalance`: Sage keys\nthe table on `vndnum`, so `id` is the only required field and the sort tiebreak\nfalls back to `_idnum`.\n\nEvery field but `id` is optional because a request may narrow the response with\n`select`."},"VendorBalance":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``: the vendor master, published as\nthe `Vendor` entity.\n\n**It is also this table's own key**, so one vendor has one row and filtering\non `vendorNumber` is how a caller reaches it."},"transactionDate":{"type":"string","format":"date","description":"`trndte`. Sage labels it **\"Startup/Adjustment Date\"**, not a transaction\ndate: it is when the opening balance was established or last adjusted, not\nthe date of any payable document."},"balance":{"$ref":"#/components/schemas/Decimal","description":"`balnce`, Sage's \"Startup/Adjustment Amount\". JSON string, native scale.\n\n**This is not the vendor's current balance.** ``endbal``, published as\n`Vendor.endingBalance`, is the running figure. This one does not move as\ninvoices are entered and paid, and reading it as an amount owed will give the\nwrong number."}},"description":"`the underlying record`, as read.\n\nA vendor satellite: one row per vendor, carrying the opening figure the vendor\nwas set up with.\n\n**This table has no `recnum`, so there is no `recordNumber` and `id` is the\nonly required field.** the underlying record is keyed on `vndnum`: the vendor's record number\n*is* the primary key. Paging is still total, because `_idnum` is present and is\nused as the sort tiebreak in place of `recnum`.\n\nEvery field but `id` is optional because a request may narrow the response with\n`select`."},"VendorCertificate":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``: the vendor master, published as the `Vendor` entity."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`. Sequential within the vendor."},"description":{"type":"string","description":"`dscrpt`. What the certificate is: free text, not a coded type."},"jobNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`jobnum`, a foreign key to ``recnum``.\n\n`the underlying record` is the **job** table, not job cost; job cost is `the underlying record`. Present\nwhen the certificate is required for one job rather than for the vendor\ngenerally, so `0` here is the ordinary case, not a broken pointer."},"dateReceived":{"type":"string","format":"date","description":"`dtercv`, Sage's \"Received\", defaulted to TODAY on entry. A date, not an instant."},"expirationDate":{"type":"string","format":"date","description":"`expdte`, Sage's \"Expires\". A date, not an instant.\n\nThe column a compliance query filters on. There is no derived \"expired\" flag\nanywhere in the read path, so comparing this against today is the caller's\njob."},"warning":{"type":"integer","format":"int16","description":"`cntwrn`, Sage's \"Warning\": warn when this certificate is expired.\n\n**Sage's dictionary types it `Yes/No` and it still arrives as `0` or `1`**,\nso it is declared as an integer rather than a boolean."},"stopPay":{"type":"integer","format":"int16","description":"`stppay`, Sage's \"Stop Pay\": block payment to this vendor while the\ncertificate is expired. A `Yes/No` column arriving as `0`/`1`; see `warning`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, Sage's \"Vendor Certificates\", as read.\n\nInsurance certificates and licences held for a vendor, optionally scoped to one\njob. This is the table behind Sage's compliance hold: a certificate whose\n`expirationDate` has passed and whose `stopPay` is set is what blocks a payment\nrun, and without this entity there is no way for a caller to find out why a\nvendor will not pay short of opening 4-4 in Sage.\n\n**`recnum` is the *vendor's* record number**, published as `vendorNumber`, so\nthis model has no `recordNumber` and `id` is the only required field. Rows are\nnumbered within the vendor by `linnum`."},"VendorContact":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``: the vendor master, published as\nthe `Vendor` entity. **The column is `recnum` and the value is the\nvendor's.**"},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`. Sequential within the vendor; unique only when paired with `vendorNumber`."},"contactName":{"type":"string","description":"`cntnme`."},"jobTitle":{"type":"string","description":"`jobttl`. Free text, not a code."},"phoneNumber":{"type":"string","description":"`phnnum`, Sage type `Phone`. The extension is a separate column this API does not select."},"email":{"type":"string","description":"`e_mail`. The underscore is Sage's own escaping of a reserved word, the same\ndevice as `state_` on `the underlying record` and `payee_` on `the underlying record`; it is a column-name\nartefact and never appears on the wire."},"cellPhone":{"type":"string","description":"`cllphn`."},"faxNumber":{"type":"string","description":"`faxnum`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, Sage's \"Accounts Payable Contacts\", as read.\n\nThe people at a vendor: many rows per vendor, numbered by `linnum`. `the underlying record`\ncarries a single `contact` string of its own, and this table is where the rest\nof them live; a caller that reads only `Vendor.contact` sees one name out of\nhowever many the install has recorded.\n\n**`recnum` here is the *vendor's* record number, not the contact's**, so it is\npublished as `vendorNumber` and this model has no `recordNumber`. `id` is the\nonly required field.\n\nSage carries columns on the underlying record that this API does not select: the phone\nextension (`phnext`), a second phone and its label (`othphn`, `othdsc`), and\nthree Outlook-sync columns. Worth knowing before anyone concludes the API\ncannot answer a question about them."},"VendorPartPrice":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``: the part master, published as\nthe `Part` entity.\n\nNot this row's own number. See the model doc."},"lineNumber":{"type":"integer","format":"int32","description":"`linnum`. Sequential within the part, `Numeric(2)`: Sage caps the list short."},"preferenceNum":{"type":"integer","format":"int16","description":"`vndprf`, Sage's \"Preference#\": where this vendor ranks for this part, `1`\nbeing first choice.\n\nNot a foreign key and not a record number, despite the `Num` in the name."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`vndnum`, a foreign key to ``recnum``: the vendor master, published as the `Vendor` entity."},"vendorOrder":{"type":"string","description":"`vndord`, Sage's \"Order#\": the vendor's own catalogue number for this part,\nfree text.\n\nNot an order and not a foreign key to `the underlying record`. It is what goes on a purchase\norder so the vendor recognises the item."},"discountRate":{"$ref":"#/components/schemas/Decimal","description":"`vnddsc`, Sage `Numeric(3,2)`, \"Disc. Rate\". A rate, not an amount."},"vendorPrice":{"$ref":"#/components/schemas/Decimal","description":"`vndprc`, Sage `Numeric(8,6)`. JSON string, native scale.\n\n**Six decimal places, and the string encoding is what preserves them.** This\nis the same scale as ``linprc`` and ``linprc``. A client that\nparses the value into a two-decimal type will round a unit price such as\n`0.003125` to `0.00`."},"lastUpdated":{"type":"string","format":"date","description":"`lstupd`. Date, not an instant.\n\n**The second of the two `lstupd` columns in the product**, the other being\n``lstupd`` on `Part`, so the underlying record is one of the entities where a\n`lastUpdated` filter is honoured rather than answered with a 501. The column\nis a Sage `Date` with no time of day, so the watermark is accurate to the day\nand no finer."}},"description":"`the underlying record`, Sage's \"Vendor Part Prices\", as read.\n\nWhat each vendor charges for a part, ranked by preference. Sage files it under\nmenu **9-2, Parts**, not under 4-4, Vendors, and that is the correct way to\nread the table: it is a part's list of vendors, not a vendor's list of parts.\n\n**`recordNumber` here is the *part's* record number.** Sage keys the underlying record on\n`recnum` targeting ``recnum``, and rows are numbered within the part by\n`linnum`. So a filter on `recordNumber` returns every vendor's price for one\npart, which is not what the name suggests. The name follows the rule every\nchild table in the product uses (a line table publishes its parent's `recnum`\nas `recordNumber`), so it is accurate about the family and misleading about\nthis row.\n\nthe underlying record is the one place the two spellings meet in one model: `recordNumber` is\nthe part and `vendorNumber` is the vendor, on the same row.\n\nEvery field but `id` and `recordNumber` is optional because a request may narrow\nthe response with `select`."},"VendorRemittance":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"vendorNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, a foreign key to ``recnum``: the vendor master, published as the `Vendor` entity."},"payeeName":{"type":"string","description":"`rmtnme`, Sage's \"Payee\": who the cheque is made out to when it is not the\nvendor.\n\nDistinct from `SecondaryPayee.payeeName` (``paynme``), which is a joint\npayee on one invoice. This one redirects every payment to the vendor."},"addressLine1":{"type":"string","description":"`rmtad1`."},"addressLine2":{"type":"string","description":"`rmtad2`."},"cityState":{"type":"string","description":"`rmtcty`, Sage's \"City/State\": **one 50-character column holding both**.\n\nNot the equivalent of ``ctynme``, which carries the city alone, so a\ncaller that needs the two apart has to parse this value."},"zipCode":{"type":"string","description":"`rmtzip`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, Sage's \"Vendor Remittance\", as read.\n\nWhere a vendor's cheque is sent when that differs from the vendor's own\naddress: a factoring company, a lockbox, a parent entity. One row per vendor.\n\n**`recnum` is the *vendor's* record number**, published as `vendorNumber`, so\nthis model has no `recordNumber` and `id` is the only required field.\n\nThe address columns are `rmt`-prefixed duplicates of the vendor's own and they\nare **not the same shape**: `the underlying record` splits city and state into `ctynme` and\n`state_`, while the underlying record holds both in one 50-character `rmtcty`. A caller\nmerging the two addresses has to parse, not just copy. See `cityState`."},"VendorType":{"type":"object","required":["recordNumber"],"properties":{"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"`recnum`, Sage's \"Vendor Type#\", `Numeric(3)`.\n\n**The row's own key, unlike the other vendor satellites**, where `recnum`\nholds the vendor's number. This is the value that appears as\n`Vendor.vendorType`."},"typeName":{"type":"string","description":"`typnme`. The label a human configured in 4-4; install-specific."}},"description":"`the underlying record`, Sage's \"Vendor Types\", as read.\n\nA two-column naming table: a number and the label the install gave it.\n\n**This is the lookup behind `Vendor.vendorType`.** Sage's ``vndtyp``\ntargets ``recnum``, so reading this table is how a caller turns a vendor's\ntype code into the name a human configured. Values are per install, so nothing\nhere is ever an enum in this specification.\n\n`ntetxt` exists on the table and this API does not select it."},"WorkersComp":{"type":"object","required":["id","recordNumber"],"properties":{"id":{"type":"string","description":"The row UUID (`_idnum`)."},"recordNumber":{"$ref":"#/components/schemas/RecordNumber","description":"The Sage record number (`recnum`). The target of ``wrkcmp`` and of `the underlying record`/`the underlying record`'s `cmpcde`."},"codeName":{"type":"string","description":"`cdenme`, the code's name: the state's classification description. Free text, set per install."},"taxState":{"type":"string","description":"`taxste`. The state whose comp schedule this code belongs to; comp classifications are state-specific."},"percentRate":{"$ref":"#/components/schemas/Decimal","description":"`pctrte`, the rate.\n\n**A rate, not an amount**, and the column name says \"percent\" while Sage's\ncomp schedules are conventionally quoted per $100 of payroll. OPDC never\ncomputes with this value, so which convention a given install stores is\nunverified; check one row against the carrier's schedule before computing\nwith it."},"maxWage":{"$ref":"#/components/schemas/Decimal","description":"`maxwge`, the wage cap above which this code stops accruing."},"isInactive":{"type":"integer","format":"int16","description":"`inactv`, a 0/1 flag stored as a smallint. Not a `boolean`; see `Part.isInactive`."},"noteText":{"type":"string","description":"`ntetxt`, a Sage memo field."}},"description":"`the underlying record`, a workers' compensation code (Sage 100 Contractor **5-3-2**), as\nread.\n\nOne row per state-defined comp classification: the rate charged per dollar of\nwages and the wage cap it applies below. It is one of the setup tables a\npayroll post expects to exist in Sage, if comp is used for rates or burden.\n\n**Two different columns point here, published under one field name.**\n``wrkcmp`` is the employee's default code and ``cmpcde`` /\n``cmpcde`` are the per-line override: one lookup, two column names, both\npublished as `compCode`. That is a naming convergence rather than a\ndivergence, and it is correct.\n\n**OPDC never computes workers' comp.** A payroll post writes the\nemployee-side journal only; comp is employer burden and is not posted, so\n`percentRate` and `maxWage` here are reference data this API reads and never\napplies. A caller reconciling a provider's comp accrual against Sage will find\nno OPDC-written entry to reconcile with.\n\n`the underlying record` registers no child relation, so no `children` argument.\n\n**Only `id` and `recordNumber` are guaranteed**; `select` may narrow the rest."}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key"}},"responses":{"BadGateway":{"description":"Sage itself refused the operation: `SAGE_ERROR`, the taxonomy's catch-all for a\ndatabase-side failure such as a constraint, a deadlock, a permission or a dropped\nconnection.\n\n**Retryable, and deliberately with no `Retry-After`.** The code is retryable but\ncarries no number, because there is no honest one to give: a deadlock clears in\nmilliseconds and a revoked permission never clears. The two statuses this\ncontract does publish a back-off for (`423`, `504`) are the two where the server\nknows what it is waiting for.\n\n**This is not a 500.** The API is a gateway and the failure is the upstream's,\nwhich is what `502` says and what lets you distinguish \"Sage said no\" from \"the\ngateway broke\" without reading prose. `SAGE_SCHEMA_MISMATCH` is split out of it\nat `422` for the one database-side failure your *request* can be wrong about;\nevery other database-side failure lands here.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"BadRequest":{"description":"Bad request. Includes unknown or misspelled fields, which v3 rejects rather than dropping.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"BadRequest2":{"description":"The server could not understand the request due to invalid syntax.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UtilityProblem"}}}},"Conflict":{"description":"The request conflicts with state the server already holds.\n\nTwo unrelated conditions share this status, and the `type`/`X-Error-Code`\ndistinguishes them:\n\n- `IDEMPOTENCY_KEY_REUSED`: the same `Idempotency-Key` was already used for a\n  **different** request (a different body, or the same body on a different\n  route). The `detail` says *\"this key was used for a different request; use a\n  new key\"*, because the commonest way to arrive here is a request that was\n  dispatched, failed downstream (a `422`, say), was corrected, and retried under\n  the same key. The corrected body **is** a different request, so the fix is a\n  new key.\n- `IDEMPOTENCY_KEY_IN_FLIGHT`: a request with this key is still running. Carries\n  `Retry-After`, and it is short: the holder either answers or its 90-second\n  lease elapses.\n- `DUPLICATE_TRANSACTION`: a `trnnum` already posted in `the underlying record` with different\n  lines. Nothing to do with the header; decided on the customer's own SQL Server,\n  not here.\n- `LOCK_CONFLICT`: an unlock attempt against a lock another user holds. Listed\n  because it shares the status, **not** because a v3 route returns it: it is\n  raised only by `/api/v2/utility/un-lock`, which has no v3 counterpart.","headers":{"Retry-After":{"required":false,"description":"Seconds to wait before retrying. Set on `IDEMPOTENCY_KEY_IN_FLIGHT`.","schema":{"type":"integer","format":"int32"}},"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Conflict2":{"description":"The request conflicts with the current state of the server.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UtilityProblem"}}}},"GatewayTimeout":{"description":"The request was accepted but the on-premises Sage service did not answer in\ntime. **The outcome is unknown.** Retry with the same `Idempotency-Key`; do not\nretry without one.","headers":{"Retry-After":{"required":false,"schema":{"type":"integer","format":"int32"}},"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"InternalError":{"description":"An unexpected server-side failure. The message is redacted; quote `correlationId`.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"InternalError2":{"description":"Server error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UtilityProblem"}}}},"Locked":{"description":"The Sage installation is locked for maintenance. The body names the holder.\n`Retry-After` is a v3 addition: v2 documented back-off in prose only.","headers":{"Retry-After":{"required":false,"description":"Seconds to wait before retrying.","schema":{"type":"integer","format":"int32"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"The entity, document or record does not exist.\n\nOn a read this is the **unknown-entity** case: `POST /api/v3/prats/query` is a\n404 whose `detail` names `prats`, rather than the framework's own 404 with no\nbody or an empty 200. 79 entity names is a lot of surface to typo, and a\nmistyped entity that answers `{\"data\": []}` looks exactly like an empty result.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotImplemented":{"description":"The server does not implement this for this entity. Distinct from 404: the\nroute exists and the entity exists, but the specific thing asked for does not\napply to it.\n\n**Only read routes answer this.** Its one instance is a change-stamp filter\n(`lastUpdated`, `createdSince`) on an entity with no such column:\n`CHANGE_STAMP_UNSUPPORTED`, with the field named in the body. Sage maintains no\ngeneral modification timestamp, so this is a permanent property of some\nentities rather than a backlog item, and no amount of waiting will make those\nentities filterable by change stamp.\n\nNo write endpoint returns this status. `POST /purchase-orders` did, on every\ncall, until it was implemented; if you are working from an older copy of this\ncontract, that is the difference.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"ServiceUnavailable":{"description":"The API key is provisioned but switched off: `twinn_enabled = false`, with the\noperator's reason in the body. `SERVICE_DISABLED`, `503`, retryable, and it\ncarries the taxonomy's `Retry-After: 300`.\n\n**Decided before the handler and before the route matters**, so it is reachable\non every v3 route (reads and writes alike) and carries no\n`Idempotency-Status`: nothing was reserved, nothing was dispatched, and your\n`Idempotency-Key` is not burned. Distinct from `423`, which is the\noperator-held *maintenance* lock on an otherwise enabled key and which the body\nattributes to a holder.","headers":{"Retry-After":{"required":false,"description":"Seconds to wait before retrying. `300`, from the taxonomy.","schema":{"type":"integer","format":"int32"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"TooManyRequests":{"description":"Rate limited. Reserved by the contract whether or not limits are enforced today.","headers":{"Retry-After":{"required":false,"schema":{"type":"integer","format":"int32"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthorized":{"description":"Authentication failed or the key is not authorised for this operation.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthorized2":{"description":"Access is unauthorized.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UtilityProblem"}}}},"Unprocessable":{"description":"The request is well-formed but cannot be applied: a closed posting period, an\nout-of-range coded value, unbalanced debits and credits, or amounts that do\nnot agree with their lines.\n\n**On a read there is exactly one condition and it is `SAGE_SCHEMA_MISMATCH`:** a\ncolumn or table the projection expects and this Sage build does not have. Sage\nschemas drift between installations, so a field this specification declares can\nbe absent from a particular customer's database.","headers":{"Idempotency-Status":{"required":false,"schema":{"type":"string","enum":["applied","replayed","unavailable"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"servers":[{"url":"https://{host}/api/v3","description":"The customer's OPDC passthrough host.","variables":{"host":{"default":"your-opdc-host","description":"The hostname you were issued. Substitute it to address your own OPDC\npassthrough instance."}}}]}