Reports and exports
Build, run and export reports in the app, the API and Glue.
Smooth POS reports let you slice your data however you need: sales by store and hour, best-selling lines, payments by method, stock on hand, gift card balances and more. Build them in the app, run them through the API, and export them as CSV for spreadsheets or other systems.
In the app
Go to Reports. Create a report, choose what it's about (its data source), pick the columns and filters, and set the period. Save it to run again later.
For the accounting statements (profit and loss, balance sheet, trial balance and the BAS report) go to Accounts → Reports.
How a report is defined
| Part | API field | What it does |
|---|---|---|
| Data source | data_source | What the report is about. See the table below. |
| Columns | columns | Each has a field, a name (used as the column heading) and an optional aggregation. |
| Filters | filters | Each has a field, a filter (=, !=, <, <=, >, >= or in), a value and an optional aggregation. |
| Sort | sort | A field, direction (asc or desc) and optional aggregation. |
| Period | period | from_date, to_date and timezone, like Australia/Sydney. |
| Compare periods | compare_periods | Extra periods to run the same report over, side by side. |
data_source | Reports on |
|---|---|
sales | Sales |
sales_lines | Sale lines |
sales_budgets | Sales budgets |
payments | Payments |
inventory | Stock on hand |
gift_cards | Gift cards |
store_credit | Store credit transactions |
loyalty_points | Loyalty point transactions |
ledger | Your general ledger |
Aggregations
A column with an aggregation of sum, count, avg, min, max or stdev is summarised, and the other columns become the grouping. So store_name plus total with sum gives total sales per store. A filter with an aggregation applies to the summarised value, so total, sum, >, 1000 keeps only stores that sold more than 1,000.
The period
The period filters by when records were created, in the timezone you give. Sales budgets use their budget date instead, and stock on hand isn't dated, so inventory reports ignore the period.
Access
Running a report needs access to its data source, so an API user can be allowed to see sales reports without seeing the ledger. Cost figures only show for users who can see costs. See API users and tokens.
Run a report through the API
Run a report with POST /report/run.json. Send the report definition and get it back with results and a summary filled in:
{
"data_source": "sales",
"columns": [
{"field": "store_name", "name": "Store"},
{"field": "total", "name": "Sales", "aggregation": "sum"},
{"field": "receipt_number", "name": "Transactions", "aggregation": "count"}
],
"sort": {"field": "total", "aggregation": "sum", "direction": "desc"},
"period": {"from_date": "2026-10-01T00:00:00", "to_date": "2026-10-31T23:59:59", "timezone": "Australia/Sydney"},
"filters": []
}In each row of results, a plain column is keyed by its field and a summarised one by the aggregation and the field, so the example gives rows like this:
{"store_name": "Bondi", "sum_total": 18450.5, "count_receipt_number": 412}summary adds up the summarised columns across the whole report, plus total_records. A field the data source doesn't have is left out. results holds up to 1,000 rows; for more, export it.
Saved reports work the same way. Get a report returns its definition under report, so you can send that to run.json with a new period. List reports, Create a report and Update a report manage them.
Export to CSV
Export a report with POST /report/export.json takes the same body and returns a CSV file of up to 10,000 rows. The column headings are your columns' names.
curl -X POST "https://api.smoothpos.net/report/export.json" \
-H "X-Smoothpos-Auth: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d @sales-by-store.json -o sales-by-store.csvSome lists have their own exports too, like Export journal entries. See Accounting sync.
Example: A weekly report, saved as a file
Glue's Email step can't attach files, so the way to share a scheduled report is to save it as a file and send the link.
Trigger: Scheduled, every Monday morning.
-
API Request: Get Private Data for your Slack webhook, Response variable
slack_res. -
API Request: Get a report for your saved report's ID, Response variable
report_res. -
Script: Set last week's dates
var def = JSON.parse($.report_res.body).report; var now = new Date(); var end = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); var start = new Date(end.getFullYear(), end.getMonth(), end.getDate() - 7); function day(d) { return d.toISOString().slice(0, 10); } def.period = {from_date: day(start) + "T00:00:00", to_date: day(end) + "T00:00:00", timezone: "Australia/Sydney"}; $.run_body = JSON.stringify(def); $.week = day(start); var pd = JSON.parse($.slack_res.body); $.slack_url = pd[Object.keys(pd)[0]].trim(); -
API Request: Run a report, Body
{{ run_body }}, Response variablerun_res. -
Script: Read the rows:
$.rows = JSON.parse($.run_res.body).results; -
File: Write the CSV, Operation Write, Format CSV, Source
rows, Destination Cloud storage, Filenameweekly-sales-{{ week }}.csv, Response variablefile. -
Send Notification: Share the link, Channel Slack, Webhook URL
{{ slack_url }}, TitleWeekly sales, MessageLast week's sales report: {{ file.url }}.
See File step for writing to FTP or SFTP instead, and Send Notification step for the message.
Updated about 22 hours ago
