Sales, finance and marketing reports
Read year-on-year Invoice sales growth
/a/Report_getYearlySalesGrowth
Compare paid Invoice creation-month net totals for a calendar year and its preceding year.
When to use this endpoint
Compare paid Invoice creation-month net totals for a calendar year and its preceding year. Use this for the matching saved Reports dashboard chart or table; the result is chart series, not raw object records or a PDF.
res[0] is growth, res[1] is previous-year sales, and res[2] is selected-year sales. Each has 12 cells, with Jan through Dec in xLabels; xLabels1 is empty.
Authentication and permissions
Authenticate with signed API-key fields or a logged-in CRM session. The handler requires login and account feature bit 8192 in employer.features.
The dispatcher rejects these legacy Report routes for a user with configured TeamAccess record scope, including assigned-only/parent-manager access or a department hierarchy scope. It returns an explicit legacy-report scope error; it does not return a silently partial chart.
For an allowed unscoped request, the handler reads the authenticated tenant and adds no Reports/Quotes/Invoices page-permission check, account-manager restriction or per-object ownership check. Selecting user IDs is a chart filter, not authorization.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
year | Integer string | Yes | Calendar year compared with year-1. Must be an integer string or integer. Blank/malformed values are rejected; no year-range validation is made. |
Example request
$report = FM_api('Report_getYearlySalesGrowth', [
'year'=>'2026',
]);
Example response
{
"res": [
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
],
"xLabels": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
"xLabels1": []
}
Representative empty-year result. The handler always initializes all 12 months.
res[0] is growth, res[1] is previous-year sales, and res[2] is selected-year sales. Each has 12 cells, with Jan through Dec in xLabels; xLabels1 is empty.
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":"This legacy report does not support your configured record scope. Use the scoped Contracts reports or table exports."} | The dispatcher detects configured TeamAccess record scope. This applies before the handler’s own checks. |
{"error":"no permission to access api"} | Account reporting feature bit 8192 is absent. |
{"error":"missing parameter: year"} | year omitted or null. Present blank scalars and malformed list controls return an invalid-parameter error instead. Required lists must be native arrays. |
{"error":"invalid parameter: daysFrom"} | A supplied numeric control is malformed, a list has the wrong shape or an unsupported token, or frequency/userType is outside 0–3. The message names the invalid control; validation occurs before chart SQL. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- Selects status=2 and sums total_net by cdate month, not paid_date. Both year queries include every owner and customer; user, legend, frequency and displayType controls are ignored.
- Each year query uses an inclusive January 1 start and exclusive following January 1 end, including every time on December 31.
- Both year sums remain integer-converted. Growth is round((current-previous)/previous*100) for a positive previous value; zero/negative previous values yield zero.
- Growth is calculated for all 12 initialized months, including those absent from current-year SQL rows. Positive previous sales with no current sales therefore yields -100; no positive baseline yields zero.
- year is integer-converted with no supported-range validation. The handler does not switch PHP timezone or accept a fiscal-year start.
- This is a single aggregate response with no pagination or checkpoint.
- The underlying aggregate queries do not exclude deleted source objects. Related label/source queries also have no general active/deleted filter. Totals can differ from active-record lists; no deletion behavior is changed by this documentation.
- Amounts are aggregate reporting numbers without currency codes or decimal preservation. Do not treat them as an invoice/quote ledger export.
- No direct mobile-app or m-service caller was found for this route. Browser report definitions are separate from the newer Report_getData template contract.
Side effects
- Reads report data only. No record, audit, status, notification, mail, report-cache or schema writes occur. Date helpers temporarily change PHP timezone on paths noted above.
See it in Fieldmotion
CRM1
Open Reports dashboard, add or preview the Year Over Year Growth report, set its filters, and inspect Network for Report_getYearlySalesGrowth. pageReportDashboard.js builds Report_get plus the saved api name and expands All selectors using reportDashboard/Constant.js; chart presentation options and labels are client-side.
CRM2
Open the saved Reports dashboard report using api=YearlySalesGrowth, change or preview its filters, and inspect Report_getYearlySalesGrowth. lib/displayReport.js builds this route dynamically from the saved definition, expands supported All selectors, and renders the returned series. The newer widgetReport.js instead calls Report_getData with a report template; its payload is a different contract.
Related endpoints
Report_getMonthlySalesGrowth— Return integer net sales and sequential percentage changes for paid Invoices across configured report buckets.Report_getAverageInvoicesPurchaseValue— Return whole-unit average net value, net total and record count for paid Invoices.