{"record":{"id":"78d7be057c65c7e9","repo":"thingsboard/thingsboard","slug":"rpcstatus-deleted","errorCode":null,"errorMessage":"RpcStatus: DELETED","messagePattern":"RpcStatus: DELETED","errorType":"http","errorClass":"ResponseStatusException","httpStatus":400,"severity":"error","filePath":"application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java","lineNumber":190,"sourceCode":"    @ResponseBody\n    public DeferredResult<ResponseEntity> getPersistedRpcByDevice(\n            @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true)\n            @PathVariable(DEVICE_ID) String strDeviceId,\n            @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)\n            @RequestParam int pageSize,\n            @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)\n            @RequestParam int page,\n            @Parameter(description = \"Status of the RPC\", schema = @Schema(allowableValues = {\"QUEUED\", \"SENT\", \"DELIVERED\", \"SUCCESSFUL\", \"TIMEOUT\", \"EXPIRED\", \"FAILED\"}))\n            @RequestParam(required = false) RpcStatus rpcStatus,\n            @Parameter(description = RPC_TEXT_SEARCH_DESCRIPTION)\n            @RequestParam(required = false) String textSearch,\n            @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {\"createdTime\", \"expirationTime\", \"request\", \"response\"}))\n            @RequestParam(required = false) String sortProperty,\n            @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {\"ASC\", \"DESC\"}))\n            @RequestParam(required = false) String sortOrder) throws ThingsboardException {\n        checkParameter(\"DeviceId\", strDeviceId);\n        if (rpcStatus != null && rpcStatus.equals(RpcStatus.DELETED)) {\n            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, \"RpcStatus: DELETED\");\n        }\n\n        TenantId tenantId = getCurrentUser().getTenantId();\n        PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);\n        DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));\n        final DeferredResult<ResponseEntity> response = new DeferredResult<>();\n\n        accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() {\n            @Override\n            public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {\n                PageData<Rpc> rpcCalls;\n                if (rpcStatus != null) {\n                    rpcCalls = rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink);\n                } else {\n                    rpcCalls = rpcService.findAllByDeviceId(tenantId, deviceId, pageLink);\n                }\n                response.setResult(new ResponseEntity<>(rpcCalls, HttpStatus.OK));\n            }","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/thingsboard/thingsboard/blob/45c30e83fa57356840d5f25d894002d94222d524/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java#L172-L208","documentation":"listRuleEngineRequestssByDevice (RPC list endpoint in RpcV2Controller) rejects RpcStatus.DELETED as a filter value with ResponseStatusException(400, 'RpcStatus: DELETED'). DELETED is a tombstone status used internally for removed persistent RPCs; it is not a queryable list status alongside QUEUED/SENT/DELIVERED/SUCCESSFUL/TIMEOUT/EXPIRED/FAILED.","triggerScenarios":"GET /api/rpc?deviceId=...&rpcStatus=DELETED (v2: /api/rpc/{deviceId}) passing DELETED explicitly in the status filter.","commonSituations":"A UI dropdown built straight from the RpcStatus enum values including DELETED; client code iterating all enum constants for filtering; log-driven tooling replaying a status seen on a deleted RPC record.","solutions":["Remove DELETED from the status filter options; only send QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, EXPIRED or FAILED","To find removed RPCs, omit the filter and inspect records whose status is DELETED in the page items instead of filtering server-side","Fix enum-driven UI to blacklist DELETED for list queries"],"exampleFix":"// before\nconst url = `/api/rpc/${deviceId}?rpcStatus=DELETED&page=0&pageSize=10`;\n\n// after\nconst url = `/api/rpc/${deviceId}?page=0&pageSize=100`;\nconst deleted = (await get(url)).data.filter(r => r.status === 'DELETED');","handlingStrategy":"validation","validationCode":"const LISTABLE = ['QUEUED','SENT','DELIVERED','SUCCESSFUL','TIMEOUT','EXPIRED','FAILED'];\nif (rpcStatus && !LISTABLE.includes(rpcStatus)) throw new Error('DELETED is not a list filter');","typeGuard":"function isListableRpcStatus(s) { return ['QUEUED','SENT','DELIVERED','SUCCESSFUL','TIMEOUT','EXPIRED','FAILED'].includes(s); }","tryCatchPattern":"catch (e) { if (e.status === 400 && /RpcStatus: DELETED/.test(e.message)) { refetchWithoutStatusFilter(); } else throw e; }","preventionTips":["Build status dropdowns from an explicit whitelist, not the raw enum","Filter DELETED rows client-side if needed"],"tags":["rpc","rest-api","validation","thingsboard"],"backgroundTag":null,"analyzedSha":"45c30e83fa57356840d5f25d894002d94222d524","analyzedAt":"2026-08-14T11:45:36.599Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}