Custom Reports
Reports the business builds itself under Reports > My reports: blocks of numbers, charts and tables, each made from fields you group by and fields you add up. The API lists, creates, changes, deletes and runs them. A report reads the same figures here as in EquipDash, in the export and in the scheduled email.
Custom reports are on the Pro plan. Other plans get 403 with the code plan_feature_required.
Endpoints
| Method | Endpoint | Description | Scope |
|---|---|---|---|
GET | /v1/reports/custom | List saved reports | reports:read |
GET | /v1/reports/custom/fields | Fields, block types, date presets and grains a report can use | reports:read |
GET | /v1/reports/custom/{id} | One report with its definition | reports:read |
GET | /v1/reports/custom/{id}/run | Run a report and get its figures | reports:read |
POST | /v1/reports/custom | Create a report | reports:write |
PATCH | /v1/reports/custom/{id} | Change its name, definition or email schedule | reports:write |
DELETE | /v1/reports/custom/{id} | Delete a report | reports:write |
The definition
{
"dates": { "preset": "last_30_days" },
"date_basis": "booked_on",
"location_id": null,
"blocks": [
{ "type": "numbers", "measures": ["sales", "bookings", "average_booking"] },
{
"type": "chart",
"title": "Sales by month",
"group": [{ "field": "booked_on", "grain": "month" }],
"measures": ["sales"]
},
{
"type": "table",
"title": "Who still owes money",
"group": ["customer"],
"measures": ["bookings", "still_owed"],
"filters": [{ "field": "still_owed", "op": "at_least", "value": 0.01 }],
"sort": { "field": "still_owed", "dir": "desc" }
},
{ "type": "note", "text": "Figures exclude cancelled bookings." }
]
}
| Key | Description |
|---|---|
dates.preset | today, yesterday, last_7_days, last_30_days, this_month, last_month, this_year, last_year, all_time or custom. custom needs from and to as YYYY-MM-DD. Dates are in the business's timezone. |
date_basis | booked_on (default): the day the booking was made, the same as Sales on the Reports page. booking_date: the day the booking is for. |
location_id | One pickup location, or null for all. |
blocks[].type | numbers (totals), chart (one figure across one field), table (lines) or note (text). Up to 12 blocks. |
blocks[].group | What each line or bar is. A field key, or { "field", "grain" } for a date field. Tables take up to 2, charts 1, numbers none. Grains: day, week, month, year, weekday. session_time takes no grain: each start time is its own line, labelled like 12:30 PM in the shop's time zone. |
blocks[].measures | What to add up. Up to 6. |
blocks[].filters | What to keep. op is is / is_not (value: a list), contains (text), at_least / at_most (a number) or older_than_days / within_days (for a date figure such as last_booking). |
Call GET /v1/reports/custom/fields for the fields this business can use. Rental fields only appear when Rentals is on, experience fields when Experiences is on.
Each block reads the narrowest data that has all its fields: whole bookings, rental items, experiences, payments or the customer list. A block that reads lines says so in source (for example "Rental items only"). Test bookings, drafts, quotes and cancelled bookings are left out, unless a status filter picks cancelled ones. Still owed is worked out per booking, exactly as on the booking page.
Run a report
GET /v1/reports/custom/{id}/run
{
"data": {
"id": 4,
"name": "Who still owes money",
"dates_label": "All time",
"from": null,
"to": null,
"blocks": [
{
"id": "b1",
"type": "table",
"title": "Who still owes money",
"columns": [
{ "key": "d0", "label": "Customer", "type": "group" },
{ "key": "m0", "label": "Bookings", "type": "count" },
{ "key": "m1", "label": "Still owed", "type": "owed" }
],
"rows": [
[ { "raw": "Jane Doe", "text": "Jane Doe" }, { "raw": 2, "text": "2" }, { "raw": 417.48, "text": "$417.48" } ]
],
"totals": [null, "$417.48"],
"source": null,
"hidden": [],
"error": null,
"truncated": false
}
]
}
}
raw is the number or value; text is formatted in the business's currency and date format. totals line up with the last columns; null where a column cannot be added up. A table shows up to 500 lines and a chart 60 (truncated says when there were more).
Create a report
POST /v1/reports/custom
{ "name": "Sales by source", "definition": { "dates": { "preset": "this_month" }, "blocks": [ { "type": "table", "group": ["booking_source"], "measures": ["sales"] } ] } }
Returns 201 with the report and its definition. A definition the report cannot use returns 422 with one sentence saying why, for example There is no field called "drop table".
Email a report on a schedule
PATCH /v1/reports/custom/{id}
{ "schedule": { "every": "weekly", "user_ids": [12, 31] } }
every is daily, weekly (Mondays) or monthly (the 1st). It goes out at 7am in the business's timezone, to team members only, with the whole report attached as a PDF. Send "schedule": null to stop it.