Skip to main content
Reports in ConveYour are generated configurations, not stored records. Asking for a report returns its definition — columns, controls, available actions — and you opt in to the rows separately. That split is the thing to understand first: a plain request gives you the shape of a report, not its contents.

Find the reports available to you

Each entry carries an id (for example contactsContactCountDashboard), a title, the fields that make up its columns, and a row_key naming the field that identifies a row. Reports that declare a parent are nested inside another report and are omitted from this list — you reach them through the parent’s child_reports.

Fetch the rows

Add data=true. Without it you get the configuration and no rows at all.
Alongside the rows you get retrieved_at (a Unix timestamp) and visual_props, which carry renderer hints for the returned data.
data=true is what makes a report expensive — it executes the underlying query. Fetch the config once, cache it, and request data only when you need rows.

Other flags

Any other query parameter is treated as report input — the filters and controls the report declares in its controls block. Send them alongside the flags:

CSV export

format=csv returns the file directly — Content-Type: text/csv with a content-disposition: attachment header, and no JSON envelope. Don’t try to parse it as JSON.
The export is silently truncated, with no warning in the file. When format=csv is set, the handler caps the report at your plan’s export limit by setting perPage to that value (limits.export, overridable per org). You get a well-formed CSV containing the first N rows and nothing indicating rows were dropped. Compare the row count against the report’s count before treating an export as complete.
The filename comes from the report’s own title, lowercased with spaces replaced by hyphens — not from anything you pass in.

Running an action on selected rows

Reports can declare row actions in their actions block. Each entry tells you its label, whether it needs confirmation, and whether it’s destructive. To run one, POST the row identifiers. The values come from the field named by the report’s row_key:
row_keys is required and must be non-empty — an empty array returns 400 No rows selected. The response shape depends on the action; only message is guaranteed.

Saved and shared reports

A custom report is a stored configuration — a report class plus saved input values. These are real records with full CRUD under /api/custom-reports, and they behave differently from the generated configs above. To hand one to someone without an API key, create a share URL:
You get back url, expires and the params encoded into the token. Recipients read it through GET /api/reports/shared/{pid}, which returns the same configuration shape — with actions filtered down to those marked shared-safe, or removed entirely.

Gotchas

A report id is not an ObjectId. Generated reports use a string key like contactsContactCountDashboard. Only custom reports use ObjectIds.
data=false turns data ON. These flags are read with (bool) on the raw query value, so any non-empty string is true — data=false, data=no and data=0 are not equivalent (0 is the only falsy one). To disable a flag, omit it entirely rather than setting it to false.
  • A report that fails validation or setup returns Report {id} could not be loaded rather than an empty result — treat it as a bad request, not an empty report.
  • params is stripped from the response unless you pass params=true, so don’t rely on it being there by default.