Run a saved report
A saved report is a ReportDefinition your tenant operators built in
the in-app Report Builder. As an integrator you can run it (paginate the
rows) or export it (Excel, PDF, or CSV) without re-creating the query
yourself. This recipe runs a report twice: once to consume the rows,
once to download the same data as Excel.
Required scopes
Section titled “Required scopes”reports:viewreports:export(for the export step)
1. Find the report
Section titled “1. Find the report”curl -sS \ -H "X-Api-Key: $AURA_API_KEY" \ "https://api.auradonors.com/api/tenants/$TENANT_ID/reports?page=1&pageSize=50" | \ jq '.items[] | {id, name, dataSourceKey, isShared}'Pick the id of the report you want. Cache it on your side — report
ids don’t change unless an operator deletes and re-creates the report.
If the report’s isShared = false and was authored by another user, your
key won’t see it (the listing filters to keys’ authorial scope). Have an
operator share the report or duplicate it under an account whose scope
your key has.
2. Run the report
Section titled “2. Run the report”REPORT_ID="00000000-0000-0000-0000-00000000abcd"
curl -sS -X POST \ -H "X-Api-Key: $AURA_API_KEY" \ "https://api.auradonors.com/api/tenants/$TENANT_ID/reports/$REPORT_ID/run?page=1&pageSize=100"The endpoint is a POST (because running has measurable side effects
— it writes a ReportRun log row) but takes page and pageSize as
query parameters. Default pageSize is 50.
The response is a ReportResultDto:
{ "columns": [ { "key": "ContactId", "label": "Contact ID", "type": "Integer" }, { "key": "ContactName", "label": "Donor", "type": "String" }, { "key": "OrderDate", "label": "Date", "type": "Date" }, { "key": "Amount", "label": "Amount", "type": "Decimal" } ], "rows": [ { "ContactId": 1834, "ContactName": "Jane Donor", "OrderDate": "2026-04-12", "Amount": 100.00 }, { "ContactId": 4012, "ContactName": "Sam Smith", "OrderDate": "2026-04-12", "Amount": 250.00 } ], "totalCount": 1432, "page": 1, "pageSize": 100}The columns array describes the shape of each row. Don’t hard-code
column keys against a saved report — operators can edit a report’s
column list and your code should adapt. Switch on the column type to
parse the values:
| Column type | JSON shape |
|---|---|
String | string |
Integer | integer |
Decimal | number (parse to decimal / fixed-point arithmetic — JS numbers lose precision past 2^53) |
Boolean | boolean |
Date | ISO-8601 date string (yyyy-MM-dd) |
DateTime | ISO-8601 timestamp (yyyy-MM-ddTHH:mm:ssZ) |
3. Walk every page
Section titled “3. Walk every page”Same pagination contract as everywhere else (see Conventions → Pagination):
public async IAsyncEnumerable<JsonElement> WalkReportRowsAsync( HttpClient http, Guid tenantId, Guid reportId){ var page = 1; while (true) { var url = $"/api/tenants/{tenantId}/reports/{reportId}/run?page={page}&pageSize=200"; using var response = await http.PostAsync(url, content: null); response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<JsonElement>(); foreach (var row in result.GetProperty("rows").EnumerateArray()) yield return row;
var total = result.GetProperty("totalCount").GetInt32(); var size = result.GetProperty("pageSize").GetInt32(); var pages = (int)Math.Ceiling((double)total / size); if (page >= pages) yield break; page++; }}Watch the size of totalCount. Reports can return tens of thousands of
rows; if you don’t actually need to consume every row, prefer a tighter
filter on the saved report (an operator change) over walking pages.
4. Export the same data to Excel
Section titled “4. Export the same data to Excel”When you don’t want to materialize the rows in memory, ask the platform to build a file. The export endpoint streams the file back as the HTTP response body:
curl -sS -X POST \ -H "X-Api-Key: $AURA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"format":"Excel"}' \ "https://api.auradonors.com/api/tenants/$TENANT_ID/reports/$REPORT_ID/export" \ -o "donor-report.xlsx"Body fields:
| Field | Values | Notes |
|---|---|---|
format | Excel / Pdf / Csv | Required. Case-sensitive — capitalize the first letter. |
The response is the file bytes with Content-Type set to the matching
MIME type (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,
application/pdf, or text/csv). The full report is rendered — there
is no per-export page parameter; an export pulls every row up to the
50,000-row platform cap.
Pre-built report types
Section titled “Pre-built report types”Same shape, different URLs. Use these for analytical reports the platform ships out of the box (donor retention, RFM scoring, fund performance, etc.):
curl -sS \ -H "X-Api-Key: $AURA_API_KEY" \ "https://api.auradonors.com/api/tenants/$TENANT_ID/reports/types"
curl -sS -X POST \ -H "X-Api-Key: $AURA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"parameters":{"asOf":"2026-01-01"}}' \ "https://api.auradonors.com/api/tenants/$TENANT_ID/reports/types/RfmScoring/run"The parameters object’s keys come from the report type’s Parameters
list returned by GET /reports/types.
Common failures
Section titled “Common failures”| Status | Body | Fix |
|---|---|---|
400 | {"": ["Report exceeds 50,000 row export cap."]} | Add a tighter date filter; export is hard-capped. |
400 | {"": ["Cohort is unavailable."]} | The saved report references a cohort that’s been deleted. Have an operator update the report. |
403 | {"title": "Missing required permission: reports:export"} | Add the scope to your key. |
404 | (empty) | Report id doesn’t exist or doesn’t belong to this tenant. |
What’s next
Section titled “What’s next”- For periodic delivery, set up a schedule from inside the app rather than polling. Schedules email rendered files on a fixed cadence.
- Subscribe to
report.created/report.updatedif you want to react to schema changes (those are part of the future webhook catalog — current catalog at Webhook events).