thingsboard/thingsboard · error · ResponseStatusException

RpcStatus: DELETED

Error message

RpcStatus: DELETED

What it means

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.

Source

Thrown at application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java:190

    @ResponseBody
    public DeferredResult<ResponseEntity> getPersistedRpcByDevice(
            @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true)
            @PathVariable(DEVICE_ID) String strDeviceId,
            @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
            @RequestParam int pageSize,
            @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
            @RequestParam int page,
            @Parameter(description = "Status of the RPC", schema = @Schema(allowableValues = {"QUEUED", "SENT", "DELIVERED", "SUCCESSFUL", "TIMEOUT", "EXPIRED", "FAILED"}))
            @RequestParam(required = false) RpcStatus rpcStatus,
            @Parameter(description = RPC_TEXT_SEARCH_DESCRIPTION)
            @RequestParam(required = false) String textSearch,
            @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "expirationTime", "request", "response"}))
            @RequestParam(required = false) String sortProperty,
            @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
            @RequestParam(required = false) String sortOrder) throws ThingsboardException {
        checkParameter("DeviceId", strDeviceId);
        if (rpcStatus != null && rpcStatus.equals(RpcStatus.DELETED)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "RpcStatus: DELETED");
        }

        TenantId tenantId = getCurrentUser().getTenantId();
        PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
        DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
        final DeferredResult<ResponseEntity> response = new DeferredResult<>();

        accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() {
            @Override
            public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
                PageData<Rpc> rpcCalls;
                if (rpcStatus != null) {
                    rpcCalls = rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink);
                } else {
                    rpcCalls = rpcService.findAllByDeviceId(tenantId, deviceId, pageLink);
                }
                response.setResult(new ResponseEntity<>(rpcCalls, HttpStatus.OK));
            }

View on GitHub (pinned to 45c30e83fa)

Solutions

  1. Remove DELETED from the status filter options; only send QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, EXPIRED or FAILED
  2. To find removed RPCs, omit the filter and inspect records whose status is DELETED in the page items instead of filtering server-side
  3. Fix enum-driven UI to blacklist DELETED for list queries

Example fix

// before
const url = `/api/rpc/${deviceId}?rpcStatus=DELETED&page=0&pageSize=10`;

// after
const url = `/api/rpc/${deviceId}?page=0&pageSize=100`;
const deleted = (await get(url)).data.filter(r => r.status === 'DELETED');
Defensive patterns

Strategy: validation

Validate before calling

const LISTABLE = ['QUEUED','SENT','DELIVERED','SUCCESSFUL','TIMEOUT','EXPIRED','FAILED'];
if (rpcStatus && !LISTABLE.includes(rpcStatus)) throw new Error('DELETED is not a list filter');

Type guard

function isListableRpcStatus(s) { return ['QUEUED','SENT','DELIVERED','SUCCESSFUL','TIMEOUT','EXPIRED','FAILED'].includes(s); }

Try / catch

catch (e) { if (e.status === 400 && /RpcStatus: DELETED/.test(e.message)) { refetchWithoutStatusFilter(); } else throw e; }

Prevention

When it happens

Trigger: GET /api/rpc?deviceId=...&rpcStatus=DELETED (v2: /api/rpc/{deviceId}) passing DELETED explicitly in the status filter.

Common situations: 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.

Related errors


AI-assisted analysis of thingsboard/thingsboard@45c30e83fa (2026-08-14). Data as JSON: /api/errors/78d7be057c65c7e9. Report an issue: GitHub.