Quotes: queries, documents and delivery
Query mapped quote rows
/a/Quotes_getDataTable
Return CRM2 quote rows with paging and linked Job, order, invoice and signature-request details.
When to use this endpoint
Use for the CRM2 Quotes table or Kanban query. This contract differs from CRM1’s positional getDT rows.
Authentication and permissions
Authenticate with signed API-key fields or a CRM session. Requires Quotes View (pageQuotes_view); non-admin users blocked by legacy crm_access bit 32 are denied. Client administrators inherit page permissions.
Quote visibility follows the account’s department/account-manager customer filters for non-admin users, including signed keys. Export routes additionally require pageMain_exportTableData. Linked Jobs require Jobs View and their TeamAccess scope; invoice enrichment requires Invoices View and the legacy invoice access check.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
fields | Native array of mapped names | No | Defaults to id and hasJobs because the handler adds these names before querying. Supply a native array for other fields; JSON text is rejected by validation. Unknown names are ignored. See mapped fields below. |
start / length / draw | Integer-compatible table controls | No | Defaults 0 / 10 / 0; offsets and sizes are not bounded. Used when isTable is nonzero/default. |
isTable / limit | Integer-compatible switches | No | isTable=0 disables table paging/counts; positive limit caps that mode, otherwise all matches are returned. Default isTable=1. |
orderBy / orderDesc | Mapped name and direction | Together | Mapped field and validated asc/desc direction. ID is a stable tie-breaker. |
order | Native DataTables order array | No | Fallback uses first column index minus one to choose the requested mapped field, and exact desc or asc. A name/direction pair is clearer. |
Mapped filters | Scalars, native lists or picker objects | No | Mapped text scalars use contains; text arrays use exact IN, numeric scalars integer-convert and numeric arrays use numeric IN. customer/user pickers accept {id} objects or lists; malformed pickers fail. |
searchField / searchValue | Mapped field name(s) and text | No | General contains search over selected fields or All. Exact searchField=status has special case-insensitive name-prefix matching; missing matches can emit a warning. |
jobId / appointmentId | Legacy string filter for Job ID | No | Canonical jobId or legacy appointmentId alias. Validated ID text adds an active permitted Job relation substring filter. |
invoices / orders | String ID/reference fragments | No | Validated fragments. Includes direct and permitted active Job-linked invoices (types 3/4), and active requisition/purchase orders targeting the quote. |
metaEmailSent | Native list of 0/1 choices | No | Native 0/1 list. Single 0 matches missing/not-sent markers within a parenthesized predicate; both choices disable the filter. |
requestSignature | Native list of statuses | No | Native list containing only 0/1 request statuses; EXISTS filtering avoids duplicated quotes. Expired request history remains visible. |
hasFiles | Native list of choices | No | CRM2’s verified choices: [0] Has File, [1] Has No File, both disable filtering. Deleted files do not count. |
customer / childJobAndAsset | Picker value / native expansion list | No | When customer is supplied, expansion choice 0 adds immediate child customer IDs. No asset expansion is implemented in this handler. |
recurringID | Positive recurrence ID | No | Filters generated occurrences for an accessible rule; requires recurringQuotes feature and Quotes View. Select recurringID in fields for response markers. |
referenceDate / dateFrom / dateTo | Mapped date alias / integer day offsets | No | Valid referenceDate enables an inclusive range from account-local today plus dateFrom to today plus dateTo end-of-day. Date-only fields use date boundaries. Defaults are not an implicit filter. |
offsetFrom / offsetTo | Native {hour,minute} objects | No | Adjust reference date bounds: hour is negated and minute added. No validation of ranges. |
timeRangeFrom / timeRangeTo | Integer hours | No | Hour-of-day filtering requires timeRangeFrom to be present; a to-only request does not activate the branch. Uses tenant timezone (Europe/London fallback for this filter). |
groupBy / advanceSearch | Native arrays | No | Mapped advanced filters are validated; groupBy and raw SQL clauses are rejected. |
Example request
$table = FM_api('Quotes_getDataTable', [
'fields'=>['id','quoteNum','customer','totalGross'],
'start'=>0,'length'=>20,'orderBy'=>'id','orderDesc'=>'desc',
]);
Example response
{"data":[{"id":"1042","quoteNum":"QT-1042","customer":{"id":"1088"},"totalGross":"120.00","hasJobs":"0","jobIDs":[],"orders":[],"requestSignature":[],"invoices":[]}],"draw":0,"recordsTotal":"1","recordsFiltered":"1"}
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error":"error in database query"} | The main mapped data SELECT fails. |
{"error":"invalid picker filter: customer"} | Malformed mapped customer picker; other picker keys use the same pattern. |
{"error":"Recurring Quotations are not enabled for this account"} | recurringID filter supplied without the feature. |
{"error":"permission denied"} | Required Quotes/Export permission is absent or legacy access restrictions apply. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- fields defaults to id/hasJobs and otherwise must be a native mapped-name list. Active quote/customer visibility is applied before paging, including signed keys. Both recordsTotal and recordsFiltered respect that visibility.
- Validates directions, native selectors, picker objects, date/hour controls and mapped advanced filters. Rejects raw SQL clauses, malformed structures and groupBy; legacy quote_ advanced IDs are normalized. Tied table rows use a stable ID order.
- Job, invoice, order and signature filters use EXISTS/subqueries rather than duplicate-producing joins. Numeric/name fragments are validated and escaped before SQL. Email-not-sent includes missing markers in a parenthesized predicate, so it cannot bypass other filters.
- The verified CRM2 file-choice contract is preserved: hasFiles=[0] means Has File, [1] means Has No File, and both choices remove the filter. Only non-deleted type-Quote files count.
- Always adds id, hasJobs, jobIDs, orders, requestSignature and invoices. Job IDs/counts use current distinct active permitted Jobs rather than stale cache. Related orders exclude deleted rows; invoices require invoice access and combine direct/type-3/4 links through active permitted Jobs.
- jobId is the canonical Job filter; appointmentId remains a compatible legacy alias. requestSignature selects request status, without hiding expired history. Recurrence filtering still requires the feature and an accessible rule.
- isTable=0 returns data without table counts and uses positive limit when supplied; otherwise all matching rows are returned. Default table paging is start=0,length=10.
- No direct caller for this endpoint was found in the mobile app or its m-service intermediary.
Side effects
- Reads active permitted records without quote cost, history, email or derived Job-cache writes.
See it in Fieldmotion
CRM1
No direct caller found. CRM1’s Quotes screen uses Quotes_getDT.
CRM2
Quotes table and Kanban load through Quotes_getDataTable. CRM2 export first obtains matching IDs with fields:[id], isTable:0, then sends batches to an exporter.
Related endpoints
Quotes_getDTis the different legacy table contract.Quote_getRequestedDetailsreturns selected details without table pagination.