Report execution has two rules that are each stated once in the reference and together cause most of the failures people meet. A session holds exactly one report result at a time, and report execution locks out a specific list of other calls for its duration. Neither produces an error that names the cause, which is why the same report works when run by hand and fails inside a loop. This page covers the sequence that actually works, the collision list, the interval flags, how to read a result, and the limit that returns nothing rather than an error.
Clean up before every execution, not only when you expect a result
The reference states the constraint directly on the execute report page: there can be only one report in a session at the same time, so if a session already holds report results they must be cleared before executing a new report, using report/cleanup_result. That makes the working sequence three calls rather than one:
svc=report/cleanup_result¶ms={}
svc=report/exec_report¶ms={...}
svc=report/get_result_rows¶ms={...}The cleanup command takes no parameters and answers with an error code of 0 on success. Call it before every execution rather than only when you believe a result is pending, because the cost is a single round trip and the alternative is a failure whose cause is a result left behind by a previous run — possibly a previous run in a different process that happened to share the session. This is the single most common reason a report that works in isolation fails the second time it is called.
Keep report execution away from the calls it collides with
The same page lists what cannot run at the same time as report/exec_report: report/export_result, report/get_result_chart, report/get_result_map, messages/load_interval, render/create_messages_layer, unit/get_trips, resource/get_driver_bindings, resource/get_trailer_bindings, every request in the Export and import chapter, and account/get_account_history. The messages/load_interval command carries the mirror-image restriction against the same list.
That collision matters because it cuts across features that look unrelated to each other. A backfill job pulling raw messages and a scheduled report generator, both authenticated as the same user, will interfere — and the symptom surfaces in whichever one happened to start second, which is rarely the one that needs fixing. The concurrency ceiling reinforces it: a session permits no more than three heavy requests simultaneously, and both message loading and report execution count as heavy. The practical answer is to separate the two concerns onto separate sessions, or to serialise them behind a single scheduler that understands they are the same resource.
Pass an explicit interval when the result is going anywhere permanent
The execution call takes the report definition, the object it runs over, and the interval:
svc=report/exec_report¶ms={
"reportResourceId":<long>,
"reportTemplateId":<long>,
"reportObjectId":<long>,
"reportObjectSecId":0,
"reportObjectIdList":[],
"interval":{"from":<uint>,"to":<uint>,"flags":0}
}| Parameter | Description |
|---|---|
reportResourceId | Resource ID. Required — with reportTemplateId of 0, the report executes as the creator of this resource |
reportTemplateId | Template ID. 0 means the template comes in reportTemplate instead |
reportObjectId | Item ID the report runs over |
reportObjectSecId | Subitem ID such as a driver or trailer; 0 when the item has none |
reportObjectIdList | Extra item IDs, for unit-group reports |
interval | from and to in UNIX time, plus flags |
remoteExec | 1 executes server-side; used with report/get_report_status |
reportTemplate | Template JSON from report/get_report_data; only when reportTemplateId is 0 |
The interval flags select a relative window instead of the literal from and to values:
| Flag | Interval |
|---|---|
0x00 | The specified interval |
0x01 | From from until today |
0x02 | Previous n days |
0x04 | Previous n weeks |
0x08 | Previous n months |
0x10 | Previous n years |
0x20 | Including the current period |
0x40 | Previous n hours, or previous n minutes |
Relative intervals are convenient for scheduled jobs and dangerous for anything reproducible, because the same call re-run tomorrow covers a different window and produces different numbers with no indication that the question changed. When a report result is going into a warehouse or onto an invoice, pass an explicit interval with flag 0x00 and record the boundaries alongside the rows, so the figure can be recomputed later and compared against itself.
Parse cells as either a string or an object
The response describes the result rather than containing it. It reports whether messages were rendered, an array of statistics, and an array of tables carrying their type, label, row count, nesting level, column count and headers. Rows come separately and in pages, through report/get_result_rows with a table index and a row range.
Each row carries its own interval metadata, which is more useful than it first appears: n is the row index from zero, i1 and i2 are the numbers of the first and last message in the interval, t1 and t2 are their times, d is the quantity of rows at the next nesting level, and c is the cell array. Those message numbers and timestamps make a row traceable back to the raw data it summarises, which is what you need when a customer disputes a total.
Two properties of the cell array shape the parser. A cell arrives as either a plain text value or as an object carrying t for the formatted text, v for the original numeric value, and x and y for coordinates — so a parser written against the first report it saw breaks on the first report containing positions, and code that reads t when it wants a number gets a display string with units and separators in it. And the row reference notes that rows come back as a flat array regardless of nesting level, so reconstructing hierarchy means walking the d field yourself rather than expecting nested structure.
Treat an empty report as a failure until proven otherwise
Report execution is metered, and the quotas are published on the current limitations page:
| Limit | Value |
|---|---|
| Online execution | 5 minutes of server time |
| Execution by notification | 5 minutes of server time |
| Execution by job | 10 minutes of server time |
| Rows in a report with detalization | 400,000 |
| Continuous execution per user from one IP | 10 minutes, then reports cannot be requested for a further 10 |
| Aggregate | A user cannot request more reports per hour than the system can execute in an hour |
The continuous-execution rule is the one a backfill meets first, and it is the reason bulk extraction is designed around small jobs rather than large ones. It measures one or more reports run back to back for one user from one IP address: cross ten minutes and the run stops, and reporting stays locked out for another ten. A single oversized job therefore costs twenty minutes rather than the time it ran for, and an extraction that hits it repeatedly spends most of its wall-clock time waiting.
The reference is explicit about what happens at the boundary, and it is the most dangerous behaviour on this page: when the time limit is reached, report execution is skipped and no results are returned. That is an absence rather than an exception. The call succeeds and the payload simply has no rows in it, so an ETL that reads an empty result as an interval with no activity writes a plausible zero into the warehouse and moves on. Nobody notices until a number is disputed, and by then the gap is indistinguishable from a period when nothing happened.
Two guards are cheap enough to apply unconditionally. Check the reported row count against a baseline you know to be non-zero — a unit count, or the previous period’s volume — before writing anything downstream. And when a report does exceed its budget, narrow it rather than retrying it: a shorter interval, fewer units, or less detalization. A bare retry repeats the same timeout, produces the same empty result, and pushes the run closer to the ten-minute continuous-execution ceiling and the lockout behind it.
This is what makes one-day-per-unit the standard shape for a backfill rather than one month or one year. A single vehicle-day is nowhere near the 400,000-row ceiling even on a busy day, it finishes far inside the execution budget so the cooldown never triggers, and it is independently re-runnable — a failed day costs one day rather than a restart. The concurrency to run those jobs has to come from separate sessions, because a session holds one report result at a time; three parallel extractions means three sessions, not one session with a semaphore in front of it.
The archived forum records how long this has been costing people. “Execute report, error” accumulated 16,705 views across 24 replies with activity as recent as 2024, and it sits alongside “How to create downloadable report” and “Help execute report!!” in a cluster of report threads that never resolved. The board is offline, every URL now redirects to the help.wialon.com root, and captured pages survive for roughly 61% of threads in the Wayback Machine.
None of this is hard to implement. It is hard to keep implemented: the caps move, the empty-result behaviour is silent, and the failure only surfaces weeks later when a number is disputed. If you would rather not own that, FleetSQL runs this shape of extraction as a managed service and lands it in a PostgreSQL database you own.
- Call report/cleanup_result before every execution, unconditionally.
- Give report generation its own session so it cannot collide with message loading.
- Alert on a zero-row report over a known-active interval, because the platform will not.