A Wialon Remote API call answers with a JSON object containing an error code and nothing else that explains it. The codes are documented and there are few of them, but they split into classes that need completely different handling, and an integration that treats them uniformly produces retry storms, temporary IP blocks and silent gaps in its own history table. This page covers the full code table, the reason strings some codes carry, the published quotas that generate the rest, and a classification you can build retry logic on.
Read the reason string, not only the code
The server returns errors as a JSON object of the form {"error":<code>}. The complete table, from the error reference:
| Code | Meaning |
|---|---|
| 0 | Successful operation (for logout, a successful exit) |
| 1 | Invalid session |
| 2 | Invalid service name |
| 3 | Invalid result |
| 4 | Invalid input |
| 5 | Error performing request |
| 6 | Unknown error |
| 7 | Access denied |
| 8 | Invalid user name or password |
| 9 | Authorization server is unavailable |
| 10 | Reached limit of concurrent requests |
| 11 | Password reset error |
| 14 | Billing error |
| 1001 | No messages for selected interval |
| 1002 | Item with such unique property already exists, or cannot be created according to billing restrictions |
| 1003 | See the reason variants below |
| 1004 | Limit of messages has been exceeded |
| 1005 | Execution time has exceeded the limit |
| 1006 | Exceeding the limit of attempts to enter a two-factor authorization code |
| 1011 | Your IP has changed or session has expired |
Error 1003 is five different failures wearing one number, and the only way to tell them apart is the reason field that accompanies it. The reference documents five variants: reason 1 means only one request is allowed at the moment, reason 2 carries the string LIMIT api_concurrent, reason 3 carries LAYERS_MAX_COUNT, reason 4 carries NO_SESSION, and reason 5 carries LOCKER_ERROR. Dispatch on the reason rather than on the code, and treat LAYERS_MAX_COUNT and LOCKER_ERROR as opaque — the reference lists the strings without expanding what they mean internally, so log them and alert on them rather than building behaviour on a guess.
Other codes carry a reason string too, and it is the difference between an actionable log line and a useless one. Error 4 means invalid input and nothing more until you read what came with it. A token of the wrong length answers with a reason of WRONG_TOKEN_LENGTH, and the message quota reports itself the same way with LIMIT msgs_activity attached to code 1004:
{"error":4,"reason":"WRONG_TOKEN_LENGTH"}
{"error":1004,"reason":"LIMIT msgs_activity"}Log the reason verbatim alongside the code in every case. An integration that records only the numeric code will spend its first production incident guessing which of a dozen possible payload problems produced error 4.
Classify codes before you retry them
Retry logic that treats every failure the same way is the most expensive mistake available here, because two of the most common codes are terminal and retrying them consumes the exact resource that recovering would need. Five classes cover everything:
| Class | Codes | Action |
|---|---|---|
| Re-auth | 1, 1011, 1003 reason 4 | One token/login round trip, then replay the call once |
| Transient | 3, 5, 6, 9, 1003 reason 5 | Exponential backoff with jitter, capped attempts |
| Throttle | 10, 1003 reasons 1 and 2, 1004 | Long backoff and reduce in-flight concurrency |
| Narrow and retry | 1005 | Shorten the interval or reduce the units, then retry |
| Terminal | 2, 4, 7, 8, 11, 14, 1002, 1006, 1003 reason 3 | Dead-letter queue, no retry |
| Not an error | 1001 | An empty interval — return zero rows |
Error 7 belongs in the terminal class because access is a property of the token’s flags, fixed at the moment the token was created, rather than a property of the request. The authorization form defaults access_type to 0x100, which grants online tracking only, so a token minted from a form that never set the parameter carries no view access and will return error 7 on data reads for as long as it exists. Retrying does not change the flag, and concurrent retries consume the session’s ten-simultaneous-request ceiling, which is what error 10 reports — so a mis-flagged token turns one configuration mistake into unrelated calls failing across the same session. The fix is to reissue the token: read-only is 0x100 combined with 0x200, which is 768 in decimal.
Error 1 also looks transient and is not. The session is invalid, and replaying the same request with the same session identifier returns the same code again; it needs a fresh token/login before the call can succeed. Cap that at one re-authentication per request, because a re-auth loop against a token that has actually been revoked walks straight into the login ceiling described below. Error 1005 deserves the same care in the opposite direction: the request exceeded its server-time budget, so a bare replay repeats the same timeout and spends the execution quota on the way. Narrow the interval, reduce the unit count or drop detalization instead.
Watch the login ceiling before you build a retry loop
The session and login quotas are the ones an integration is most likely to breach by accident, and the consequence reaches beyond the integration that caused it:
| Limit | Value |
|---|---|
| Failed logins per IP per minute | 10 |
| Successful logins per IP per minute | 120 |
| Active sessions of one user from one IP | 100 |
| Active tokens per user | 1,000 |
| Password reset attempts per minute | 1 |
Exceeding these produces a temporary IP-address block. That is why error 8 is the expensive terminal in the table above: more than ten failed logins from one address in a minute takes down every integration sharing that egress address, not only the one holding the bad credential. A worker that responds to a bad token by re-authenticating in a loop will reach the ceiling in under a minute and take its neighbours with it.
Budget message loading against five separate ceilings
Codes 1004 and 1003 exist because message loading is metered, and it is metered several ways at once rather than one. All of the following come from the current limitations page:
| Limit | Value |
|---|---|
| Loaded into all sessions of one user | 15,000,000 messages |
| Loaded by one user within 2 minutes | 15,000,000 messages |
| Imported within 1 minute | 500,000 messages |
| Loaded by one user within 1 hour | 200,000,000 messages |
| Message layers | 50 |
| Messages on request | 2 GB |
After a bound is reached the user cannot load or import messages for the remainder of that period, and the documentation notes that this breaks report execution and track building as well. The quota is attached to the user rather than to a feature, which is the part that surprises people: a backfill job saturating the two-minute window degrades the live interface running under the same account, and the operator experiencing it has no way to connect the symptom to the job. Give extraction work its own account where the customer’s licensing allows it, and pace it against the two-minute window rather than against wall-clock throughput.
Guard report results against the limit that returns nothing
Report execution has its own budget, and it fails in a way that no error code describes:
| 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 |
That continuous-execution rule is the one bulk extraction meets first, and it bites twice: exceeding it stops the current run and then locks reporting out for ten minutes, which turns a single oversized job into a twenty-minute hole in a backfill.
The reference is explicit about the boundary: 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, the payload contains no rows, and an ETL that reads an empty result as an interval with no activity writes a plausible zero into the warehouse. Nobody notices until a customer disputes a number weeks later, at which point the gap is indistinguishable from a genuinely quiet period. Two guards are cheap enough to be unconditional: compare the returned row count against a baseline you know to be non-zero before writing anything, and treat an empty result over a period with known activity as a failure rather than as data.
This is also the point where the archived forum is worth citing. “Geofence export data doesn’t have info” accumulated 11,747 views before the board went offline, and it is one of a cluster of export and report threads that never resolved. Every forum URL now redirects to the help.wialon.com root rather than to a matching page, which Google’s own site-move guidance warns may be treated as a soft 404, and captured pages survive for roughly 61% of threads in the Wayback Machine.
Size concurrency to the session, not to your worker pool
The last group of quotas is the one that determines how much parallelism is available at all, and it is measured per session rather than per account or per process:
| Limit | Value |
|---|---|
| Heavy requests simultaneously per session | 3 |
| API requests simultaneously per session | 10 |
| Map tracings simultaneously per session | 3 |
| avl_evts requests per 10 seconds per session | 10 |
| core/check_unique requests per minute | 30 |
| Tile rendering per IP | 200 seconds of server time |
| Tile rendering per IP per user | 120 seconds of server time |
| Largest integer safe in a double parameter | 9007199254740991 — use long above it |
Message loading and report execution both count as heavy, so three concurrent extractions is the ceiling for a session no matter how many workers are pointed at it. Adding workers past that point does not increase throughput; it converts would-be work into error 10 and error 1003, which then consume retry budget. The avl_evts ceiling matters for the same reason in the opposite direction — an event loop polling once per second is already at the published limit before anything else is running.
- Give each worker its own session rather than sharing one, so the three-heavy-request ceiling applies per worker instead of across the pool.
- Record the reason string with every error, not only the code, or error 4 and error 1003 stay undiagnosable.
- Alert on a zero-row report over a known-active interval, because the platform will not tell you that one happened.