Run a report through REST
Running a report over REST isn’t like calling a normal business object method. A report method doesn’t
hand the report back in its response. Epicor sends the job to SSRS, SSRS renders it, and Epicor stores
the result in the report list table (SysRptLst), which is what the System Monitor’s Reports
list shows. Your program then has to fetch it from there.
The flow
Section titled “The flow”- Get the report’s parameters from its report service (
Erp.RPT.<Report>Svc), starting from the defaults. - Change the parameters you need, including two that make the report render to the report list:
AutoActionandSSRSRenderFormat. - Call
RunDirectwith the parameters. A200response means the report has been generated. - Read the newest matching row from
SysRptLstthrough a BAQ, take itsRptDatafield, and Base64-decode it. - Save or parse the bytes: write a PDF to disk, or read a CSV straight into your program.
Before you start
Section titled “Before you start”Create a BAQ, for example XX_ReportOutput, on Ice.SysRptLst:
- Display fields:
SysRptLst.RptData, plus whatever identifies the row (its key, description and creation date and time) - Criteria: limit to the API user’s own rows, and if you can, to the marker described below
- Sort: newest first
Example (Python)
Section titled “Example (Python)”This runs the WIP report for a date range and reads it as CSV. It reuses the session and odata()
helpers from Call Epicor from another application.
import base64, csv
REPORT = "Erp.RPT.WIPReportSvc"
# 1. Default parametersdefaults = session.post(odata(f"{REPORT}/GetDefaults"), json={"ds": None})defaults.raise_for_status()ds = defaults.json()["parameters"]["ds"]
# 2. Adjust themparam = ds["WIPParam"][0]param["AutoAction"] = "SSRSPreview" # render into the report listparam["SSRSRenderFormat"] = "CSV" # or "PDF"param["InclJobsNotClosed"] = Trueparam["JobClosedStartDate"] = "2025-01-01T00:00:00Z"param["JobClosedEndDate"] = "2025-01-31T00:00:00Z"
# 3. Run itrun = session.post(odata(f"{REPORT}/RunDirect"), json={"ds": ds})run.raise_for_status()
# 4. Fetch the newest outputout = session.get(odata("BaqSvc/XX_ReportOutput/Data"), params={"$top": 1})out.raise_for_status()encoded = out.json()["value"][0]["SysRptLst_RptData"]report_bytes = base64.b64decode(encoded, validate=True)
# 5a. Parse a CSVfor row in csv.DictReader(report_bytes.decode("utf-8").splitlines()): print(row)
# 5b. Or save a PDF# if report_bytes[:4] == b"%PDF":# open("wip.pdf", "wb").write(report_bytes)Parameter names differ for every report. Open the report service in the REST help page, call
GetDefaults there, and look at the parameter table it returns (here WIPParam) to see what you can
set.
Finding the right output reliably
Section titled “Finding the right output reliably”“The newest row” is only safe if nothing else runs the same report under the same user at the same time. For anything busier, tag the run:
- Put a unique value (a GUID) in the parameter row’s
TaskNotebefore callingRunDirect. - Filter the BAQ on the report list’s
RptNotefor that value (make it a BAQ parameter).
That should find exactly your run’s output, however many other reports are being printed.
Things to know
Section titled “Things to know”RunDirectwaits for the report. A large report can take longer than your HTTP client’s default timeout; raise the timeout for this call. For very large reports, submitting the task to the System Agent and polling for the output is kinder to the server.- Clean up. Report list rows are kept until they’re purged. A frequently run integration can fill the list; make sure your system’s report cleanup covers the API user.
- Access. The API user needs security access to the report service and the BAQ, and to any data the report reads.
- Inside Epicor, the same idea works from an Epicor Function, which can also email the result: see Email a report as a PDF attachment.