Reports

Create and run CRM reports from a fixed metric catalog, and freeze snapshots of their results.

The Reports API saves reports (a layout of widgets), runs their widgets against live CRM data, and freezes snapshots of the results. Every widget names a metric from a fixed catalog, so a report can never run an arbitrary query.

These endpoints take a signed-in member's session token (Authorization: Bearer <session-token>); API keys do not open them. See Authentication.

Who sees which data

The server works out the scope on every run:

  • A member sees only records they are assigned to. Any assigneeIds they send is ignored.
  • An admin may send assigneeIds to report on those people. Without it, an admin sees only their own records.
  • Metrics marked admin only return an empty series to a member instead of an error.

A member who opens a report an admin shared still sees only their own data.

Run widgets

POST /crm-reports/metrics/run

Runs a batch of widgets and returns one series per widget, keyed by instanceId. Nothing is saved.

ParameterTypeRequiredDescription
widgetsBody, arrayYesEach { instanceId, metricId, chart, groupBy?, timeframe?, filters? }
widgets[].metricIdBody, stringYesA metric id from the catalog
widgets[].chartBody, stringYesbar, line, area, pie, donut, radar, radial, stat, leaderboard or table
widgets[].groupByBody, stringNoSplit the result: status, source, language, country, currency, stage, direction, outcome or assignee, where the metric supports it
widgets[].timeframeBody, objectNoOverrides the report timeframe for this widget
widgets[].filtersBody, objectNoNarrow the rows before they are counted (see Widget filters)
timeframeBody, objectYes{ preset, from?, to?, bucket }
assigneeIdsBody, string[]NoAdmins only: the people to report on

timeframe.preset is last_7d, last_30d, last_90d, this_month, last_month, this_quarter, last_quarter, ytd or custom (then send from and to as ISO dates). bucket is day, week, month or quarter.

bash
curl -X POST https://api.fondaro.com/crm-reports/metrics/run \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{    "timeframe": { "preset": "last_30d", "bucket": "day" },    "widgets": [      { "instanceId": "w1", "metricId": "leads.created_over_time", "chart": "area" },      { "instanceId": "w2", "metricId": "deals.win_rate", "chart": "stat" }    ]  }'
json
{  "results": [    {      "instanceId": "w1",      "shape": "timeseries",      "series": [{ "key": "value", "label": "New leads" }],      "points": [{ "bucket": "2026-09-01", "value": 12 }]    },    {      "instanceId": "w2",      "shape": "stat",      "series": [],      "points": [],      "stat": { "value": 34.5, "previous": 30.1, "deltaPct": 14.6, "unit": "%" }    }  ]}

shape is timeseries (points carry bucket), category (points carry category), stat (a stat object with value, and optionally previous, deltaPct, unit, currency) or leaderboard. An unknown metricId returns an empty series. Widgets run six at a time.

Widget filters

filters is a map of filter id to a value or a list of values. A single value may be sent as a string.

KeyApplies toValue
tagLead metricsTag ids; keeps leads carrying any of them
sourceLead metricsLead sources
statusLead metricsLead statuses
invoicedNotCollectedDeal metrics that offer ittrue or "true" to switch on
collectionDueThisMonthDeal metrics that offer ittrue or "true" to switch on
commissionsUnpaidDeal metrics that offer ittrue or "true" to switch on

Unknown keys are ignored. Several deal filters together must all match.

Metric catalog

CategoryMetric ids
Leadsleads.created_over_time, leads.by_status, leads.by_source, leads.by_language, leads.by_country, leads.qualification_rate, leads.status_funnel, leads.avg_time_in_status, leads.purchased_revenue, leads.by_tag, leads.time_to_first_answer
Dealsdeals.created_over_time, deals.pipeline_by_stage, deals.pipeline_value_by_stage, deals.win_rate, deals.won_lost_over_time, deals.revenue_won_over_time, deals.avg_deal_size, deals.avg_time_to_close, deals.lost_reasons, deals.avg_time_in_stage, deals.stalled, deals.commission_won_over_time, deals.commission_breakdown, deals.commission_by_agent and deals.commission_due_by_agent (admin only)
Taskstasks.completed_over_time, tasks.completion_rate, tasks.overdue, tasks.status_split
Callscalls.over_time, calls.by_outcome, calls.answer_rate, calls.avg_duration, calls.cost_over_time, calls.sentiment, calls.top_objections
Emails and messagesemails.sent_over_time, emails.by_status, messages.by_channel
Teamteam.leaderboard, team.activity_by_assignee, team.time_to_first_answer (all admin only)

A metric's shape decides its charts: timeseries takes line, area or bar; category takes bar, pie, donut or radar; stat takes stat or radial; leaderboard takes leaderboard or table.

Reports

List reports

GET /crm-reports

Returns the reports you can see, most recently updated first: your own and those shared with the organization (an admin sees all). Each carries snapshotCount and latestSnapshotAt.

bash
curl https://api.fondaro.com/crm-reports \  -H "Authorization: Bearer $SESSION_TOKEN"
json
[  {    "id": "c4e1...",    "ownerId": "user_2abc...",    "title": "Monthly pipeline",    "description": null,    "layout": { "widgets": [] },    "defaultTimeframe": { "preset": "last_30d", "bucket": "day" },    "savedAssigneeIds": null,    "visibility": "organization",    "aiGenerated": false,    "snapshotCount": 2,    "latestSnapshotAt": "2026-09-30T08:00:00.000Z",    "createdAt": "2026-09-01T10:00:00.000Z",    "updatedAt": "2026-09-30T08:00:00.000Z"  }]

Create a report

POST /crm-reports

Returns the new report. Every widget's metricId and chart are checked against the catalog.

ParameterTypeRequiredDescription
titleBody, stringYesUp to 200 characters
descriptionBody, string or nullNo
layoutBody, objectNo{ widgets: [...] }. Each widget is { instanceId, metricId, chart, title?, groupBy?, timeframe?, filters?, layout: { x, y, w, h } } on a 12-column grid
defaultTimeframeBody, objectNo{ preset, from?, to?, bucket }
savedAssigneeIdsBody, string[] or nullNoThe people an admin's snapshot reports on
visibilityBody, private or organizationNoWho else can open it
bash
curl -X POST https://api.fondaro.com/crm-reports \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{    "title": "Monthly pipeline",    "visibility": "organization",    "layout": { "widgets": [      { "instanceId": "w1", "metricId": "deals.pipeline_by_stage", "chart": "bar",        "layout": { "x": 0, "y": 0, "w": 6, "h": 4 } }    ] }  }'
json
{ "id": "c4e1...", "title": "Monthly pipeline", "visibility": "organization", "aiGenerated": false }

Read, change or delete a report

EndpointPurpose
GET /crm-reports/:idRead one report
PATCH /crm-reports/:idChange any of the create fields
DELETE /crm-reports/:idDelete the report and its snapshots. Returns 204

Anyone who can see a report can read it; only its owner or an admin can change, delete or snapshot it.

bash
curl -X PATCH https://api.fondaro.com/crm-reports/c4e1... \  -H "Authorization: Bearer $SESSION_TOKEN" \  -H "Content-Type: application/json" \  -d '{ "title": "Pipeline this quarter", "defaultTimeframe": { "preset": "this_quarter", "bucket": "week" } }'
json
{ "id": "c4e1...", "title": "Pipeline this quarter", "defaultTimeframe": { "preset": "this_quarter", "bucket": "week" } }

Snapshots

A snapshot freezes the report's layout and computed results under your scope at that moment. It expires 180 days after it is made or renewed and is deleted 30 days after expiry.

Freeze a snapshot

POST /crm-reports/:id/snapshots

bash
curl -X POST https://api.fondaro.com/crm-reports/c4e1.../snapshots \  -H "Authorization: Bearer $SESSION_TOKEN"
json
{  "id": "9a7b...",  "reportId": "c4e1...",  "createdBy": "user_2abc...",  "title": "Monthly pipeline (2026-10-02)",  "layout": { "widgets": [] },  "data": { "w1": { "instanceId": "w1", "shape": "category", "series": [], "points": [] } },  "assigneeIds": ["user_2abc..."],  "timeframe": { "from": "2026-09-02", "to": "2026-10-02", "bucket": "day" },  "expiresAt": "2027-03-31T10:00:00.000Z",  "aiGenerated": false,  "createdAt": "2026-10-02T10:00:00.000Z"}

Other snapshot endpoints

EndpointPurpose
GET /crm-reports/:id/snapshotsList a report's snapshots, newest first
GET /crm-reports/snapshots/:snapshotIdRead one snapshot
POST /crm-reports/snapshots/:snapshotId/renewSet its expiry to 180 days from now
DELETE /crm-reports/snapshots/:snapshotIdDelete it. Returns 204

Errors

StatusWhen
400Validation failed, an unknown metric, or a chart the metric does not support
401No valid session token
403You cannot see the report, or you are not its owner or an admin when changing it
404The report or snapshot does not exist in your organization