{"record":{"id":"4eab7c8746ef60a8","repo":"thingsboard/thingsboard","slug":"bad-request-params-4eab7c","errorCode":"BAD_REQUEST_PARAMS","errorMessage":"The device must be a gateway!","messagePattern":"The device must be a gateway!","errorType":"exception","errorClass":"ThingsboardException","httpStatus":400,"severity":"warning","filePath":"application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java","lineNumber":159,"sourceCode":"                    @ApiResponse(\n                            responseCode = \"200\",\n                            description = \"OK\",\n                            content = @Content(\n                                    mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE,\n                                    schema = @Schema(type = \"string\", format = \"binary\")\n                            )\n                    )\n            })\n    @RequestMapping(value = \"/device-connectivity/gateway-launch/{deviceId}/docker-compose/download\", method = RequestMethod.GET)\n    @ResponseBody\n    public ResponseEntity<org.springframework.core.io.Resource> downloadGatewayDockerCompose(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION)\n                                                                                             @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException, IOException {\n        checkParameter(DEVICE_ID, strDeviceId);\n        DeviceId deviceId = new DeviceId(toUUID(strDeviceId));\n        Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS);\n\n        if (!checkIsGateway(device)) {\n            throw new ThingsboardException(\"The device must be a gateway!\", ThingsboardErrorCode.BAD_REQUEST_PARAMS);\n        }\n\n        String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);\n        var dockerCompose = checkNotNull(deviceConnectivityService.createGatewayDockerComposeFile(baseUrl, device), \"Failed to create docker-compose.yml file!\");\n\n        return ResponseEntity.ok()\n                .header(HttpHeaders.CONTENT_DISPOSITION, \"attachment;filename=\" + DOCKER_COMPOSE_YML)\n                .header(\"x-filename\", DOCKER_COMPOSE_YML)\n                .contentLength(dockerCompose.contentLength())\n                .contentType(MediaType.APPLICATION_OCTET_STREAM)\n                .body(dockerCompose);\n    }\n\n    private static boolean checkIsGateway(Device device) {\n        return device.getAdditionalInfo().has(DataConstants.GATEWAY_PARAMETER) &&\n                device.getAdditionalInfo().get(DataConstants.GATEWAY_PARAMETER).asBoolean();\n    }\n}","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/thingsboard/thingsboard/blob/45c30e83fa57356840d5f25d894002d94222d524/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java#L141-L177","documentation":"The gateway-launch docker-compose endpoint builds a compose file that provisions the target device as an IoT gateway. Only devices flagged as gateways (additionalInfo.gateway = true) are valid targets, so any non-gateway device id is rejected with BAD_REQUEST_PARAMS before the compose file is generated.","triggerScenarios":"GET /api/device-connectivity/gateway-launch/{deviceId}/docker-compose/download where the device was created without the 'Is gateway' flag — typically the user picked the wrong device or created it via API without additionalInfo.gateway=true.","commonSituations":"Provisioning a regular device and then trying to fetch gateway launch instructions for it; device created via REST/mqtt claim without the gateway flag; UI dropdown listing all devices instead of only gateways.","solutions":["Pick a device that is actually a gateway (device.additionalInfo.gateway == true).","Convert the device: call saveDevice with additionalInfo {\"gateway\": true}.","If a new gateway is needed, create it via POST /api/device with the gateway flag set, then retry the download."],"exampleFix":"// before\nDevice device = new Device();\ndevice.setName(\"sensor-1\"); // no gateway flag -> download fails\n\n// after\nDevice device = new Device();\ndevice.setName(\"gw-1\");\ndevice.setAdditionalInfo(JacksonUtil.newObjectNode().put(\"gateway\", true));","handlingStrategy":"type-guard","validationCode":"Device device = deviceClient.getDeviceById(deviceId);\nboolean gateway = device.getAdditionalInfo() != null && device.getAdditionalInfo().has(\"gateway\") && device.getAdditionalInfo().get(\"gateway\").asBoolean();","typeGuard":"boolean isGatewayDevice(Device d) {\n    return d.getAdditionalInfo() != null\n        && d.getAdditionalInfo().path(\"gateway\").asBoolean(false);\n}","tryCatchPattern":"try {\n    return connectivityClient.downloadGatewayDockerCompose(deviceId);\n} catch (ThingsboardException e) {\n    if (\"The device must be a gateway!\".equals(e.getMessage())) {\n        return promptUserToCreateGateway(); // or set the flag then retry\n    }\n    throw e;\n}","preventionTips":["Filter device pickers to gateway=true when launching gateway flows.","Set additionalInfo.gateway=true at creation time for gateway devices.","Provide a 'convert to gateway' action instead of failing the download."],"tags":["device","gateway","docker-compose","device-connectivity"],"backgroundTag":null,"analyzedSha":"45c30e83fa57356840d5f25d894002d94222d524","analyzedAt":"2026-08-14T11:45:36.599Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}