> ## Documentation Index
> Fetch the complete documentation index at: https://developers.conveyour.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Running Reports

> List the reports available to your org, fetch their data, and run row actions.

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

```bash theme={null}
curl --request GET \
  --url 'https://acme.conveyour.com/api/reports' \
  --header 'x-conveyour-token: <api-key>'
```

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.

```bash theme={null}
curl --request GET \
  --url 'https://acme.conveyour.com/api/reports/contactsContactCountDashboard?data=true' \
  --header 'x-conveyour-token: <api-key>'
```

Alongside the rows you get `retrieved_at` (a Unix timestamp) and `visual_props`, which carry
renderer hints for the returned data.

<Note>
  `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.
</Note>

## Other flags

| Parameter | Effect |
| - | - |
| `data=true` | Include the rows, plus `retrieved_at` and `visual_props` |
| `params=true` | Include the resolved query parameters under `params` |
| `format=csv` | Return a CSV file instead of JSON — see below |
| `debug` | Include a `debug` payload (also present automatically outside production) |

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:

```bash theme={null}
curl --request GET \
  --url 'https://acme.conveyour.com/api/reports/contactsContactCountDashboard?data=true&group_by=crew_role' \
  --header 'x-conveyour-token: <api-key>'
```

## 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.

<Warning>
  **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.
</Warning>

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`:

```bash theme={null}
curl --request POST \
  --url 'https://acme.conveyour.com/api/reports/formsFormSubmissions/action/export_selected' \
  --header 'x-conveyour-token: <api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "row_keys": ["6a1445f03af9bb089d10e1ad", "6a1445f03af9bb089d10e1ae"],
    "params": { "file_name": "Q3 Submissions" }
  }'
```

`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:

```bash theme={null}
curl --request POST \
  --url 'https://acme.conveyour.com/api/custom-reports/{id}/share' \
  --header 'x-conveyour-token: <api-key>' \
  --header 'Content-Type: application/json' \
  --data '{ "params": { "crew_role": "Installer" }, "expires": "+8 hours" }'
```

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

<Warning>
  **A report id is not an ObjectId.** Generated reports use a string key like `contactsContactCountDashboard`. Only
  custom reports use ObjectIds.
</Warning>

<Warning>
  **`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`.
</Warning>

* 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.