I’m trying to get the ChirpStack API working using node.js. I’m specifically trying to perform API actions on the public Helium LNS managed by Meteoscientific. I’m ending up getting 400-not-found errors when I see gRPC requests to the same URLs work in the browser.
Here’s the code, slightly amended from the sample to use the same API call as I see on one of the Web UI pages:
import grpc from "@grpc/grpc-js"
import device_grpc from "@chirpstack/chirpstack-api/api/device_grpc_pb.js"
import device_pb from "@chirpstack/chirpstack-api/api/device_pb.js"
// This must point to the ChirpStack API interface.
const server = "console.meteoscientific.com:443";
// The API token (can be obtained through the ChirpStack web-interface).
const apiToken = "eyJ0eX...PSFI"
// Create the client for the DeviceService.
const deviceService = new device_grpc.DeviceServiceClient(
server,
grpc.credentials.createSsl(),
);
// Create the Metadata object.
const metadata = new grpc.Metadata();
metadata.set("authorization", "Bearer " + apiToken);
// ListDevices
const ldreq = new device_pb.ListDevicesRequest(100, 0)
deviceService.list(ldreq, metadata, (err, resp) => {
if (err !== null) {
console.log(err);
return;
}
console.log("Got device list: " + resp.total_count);
});
When I run this with verbose debug I see:
D 2025-02-24T05:51:24.278Z | v1.12.6 281466 | resolving_call | [0] ended with status: code=13 details="Received HTTP status code 400"
Error: 13 INTERNAL: Received HTTP status code 400
The request goes to /api.DeviceService/List
:
D 2025-02-24T05:51:23.200Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 createLoadBalancingCall [2] method="/api.DeviceService/List"
Interestingly, in the browser when I look at the device list for one of my applications I see:
So hostname, port, and URI are correct. If auth was wrong I’d expect to seeswomething better than 400 but I don’t know enough about ChirpStack’s API implementation.
Here’s the full debug log:
Summary
D 2025-02-24T05:51:23.113Z | v1.12.6 281466 | resolving_load_balancer | dns:console.meteoscientific.com:443 IDLE
-> IDLE
D 2025-02-24T05:51:23.115Z | v1.12.6 281466 | connectivity_state | (1) dns:console.meteoscientific.com:443 IDLE -
> IDLE
D 2025-02-24T05:51:23.115Z | v1.12.6 281466 | dns_resolver | Resolver constructed for target dns:console.meteosci
entific.com:443
D 2025-02-24T05:51:23.117Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 Channel construct
ed with options {}
D 2025-02-24T05:51:23.117Z | v1.12.6 281466 | channel_stacktrace | (1) Channel constructed
at new InternalChannel (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/internal-cha
nnel.js:288:23)
at new ChannelImplementation (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/channe
l.js:35:32)
at new Client (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/client.js:66:36)
at new ServiceClientImpl (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/make-clien
t.js:59:5)
at file:///home/tve/Projects/Helium/chirp-api/add-device.mjs:16:23
at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:336:24)
at async loadESM (node:internal/process/esm_loader:34:7)
at async handleMainPromise (node:internal/modules/run_main:106:12)
D 2025-02-24T05:51:23.118Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 createResolvingCa
ll [0] method="/api.DeviceService/List", deadline=Infinity
D 2025-02-24T05:51:23.118Z | v1.12.6 281466 | resolving_call | [0] Created
D 2025-02-24T05:51:23.118Z | v1.12.6 281466 | resolving_call | [0] Deadline: Infinity
D 2025-02-24T05:51:23.119Z | v1.12.6 281466 | resolving_call | [0] start called
D 2025-02-24T05:51:23.129Z | v1.12.6 281466 | dns_resolver | Looking up DNS hostname console.meteoscientific.com
D 2025-02-24T05:51:23.130Z | v1.12.6 281466 | resolving_load_balancer | dns:console.meteoscientific.com:443 IDLE
-> CONNECTING
D 2025-02-24T05:51:23.130Z | v1.12.6 281466 | connectivity_state | (1) dns:console.meteoscientific.com:443 IDLE -
> CONNECTING
D 2025-02-24T05:51:23.130Z | v1.12.6 281466 | resolving_call | [0] startRead called
D 2025-02-24T05:51:23.131Z | v1.12.6 281466 | resolving_call | [0] write() called with message of length 0
D 2025-02-24T05:51:23.131Z | v1.12.6 281466 | resolving_call | [0] halfClose called
D 2025-02-24T05:51:23.192Z | v1.12.6 281466 | dns_resolver | Resolved addresses for target dns:console.meteoscien
tific.com:443: [193.25.200.73:443]
D 2025-02-24T05:51:23.193Z | v1.12.6 281466 | pick_first | updateAddressList([193.25.200.73:443])
D 2025-02-24T05:51:23.194Z | v1.12.6 281466 | pick_first | connectToAddressList([193.25.200.73:443])
D 2025-02-24T05:51:23.195Z | v1.12.6 281466 | subchannel | (1) 193.25.200.73:443 Subchannel constructed with opti
ons {}
D 2025-02-24T05:51:23.195Z | v1.12.6 281466 | subchannel_refcount | (1) 193.25.200.73:443 refcount 0 -> 1
D 2025-02-24T05:51:23.195Z | v1.12.6 281466 | subchannel_refcount | (1) 193.25.200.73:443 refcount 1 -> 2
D 2025-02-24T05:51:23.196Z | v1.12.6 281466 | pick_first | Start connecting to subchannel with address 193.25.20$
.73:443
D 2025-02-24T05:51:23.196Z | v1.12.6 281466 | pick_first | IDLE -> CONNECTING
D 2025-02-24T05:51:23.196Z | v1.12.6 281466 | resolving_load_balancer | dns:console.meteoscientific.com:443 CONNE
CTING -> CONNECTING
D 2025-02-24T05:51:23.196Z | v1.12.6 281466 | connectivity_state | (1) dns:console.meteoscientific.com:443 CONNEC
TING -> CONNECTING
D 2025-02-24T05:51:23.196Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 callRefTimer.unre
f | configSelectionQueue.length=0 pickQueue.length=0
D 2025-02-24T05:51:23.197Z | v1.12.6 281466 | subchannel | (1) 193.25.200.73:443 IDLE -> CONNECTING
D 2025-02-24T05:51:23.198Z | v1.12.6 281466 | transport | dns:console.meteoscientific.com:443 creating HTTP/2 ses
sion to 193.25.200.73:443
D 2025-02-24T05:51:23.200Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 createRetryingCal
l [1] method="/api.DeviceService/List"
D 2025-02-24T05:51:23.200Z | v1.12.6 281466 | resolving_call | [0] Created child [1]
D 2025-02-24T05:51:23.200Z | v1.12.6 281466 | retrying_call | [1] start called
D 2025-02-24T05:51:23.200Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 createLoadBalanci
ngCall [2] method="/api.DeviceService/List"
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | retrying_call | [1] Created child call [2] for attempt 1
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | load_balancing_call | [2] start called
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | load_balancing_call | [2] Pick called
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | load_balancing_call | [2] Pick result: QUEUE subchannel: null statu
s: undefined undefined
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 callRefTimer.ref
| configSelectionQueue.length=0 pickQueue.length=1
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | retrying_call | [1] startRead called
D 2025-02-24T05:51:23.201Z | v1.12.6 281466 | load_balancing_call | [2] startRead called
D 2025-02-24T05:51:23.202Z | v1.12.6 281466 | retrying_call | [1] write() called with message of length 5
D 2025-02-24T05:51:23.202Z | v1.12.6 281466 | load_balancing_call | [2] write() called with message of length 5
D 2025-02-24T05:51:23.202Z | v1.12.6 281466 | retrying_call | [1] halfClose called
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | subchannel | (1) 193.25.200.73:443 CONNECTING -> READY
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | pick_first | Pick subchannel with address 193.25.200.73:443
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | subchannel_refcount | (1) 193.25.200.73:443 refcount 2 -> 3
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | subchannel_refcount | (1) 193.25.200.73:443 refcount 3 -> 2
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | pick_first | CONNECTING -> READY
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | resolving_load_balancer | dns:console.meteoscientific.com:443 CONNE
CTING -> READY
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | channel | (1) dns:console.meteoscientific.com:443 callRefTimer.unre
f | configSelectionQueue.length=0 pickQueue.length=0
D 2025-02-24T05:51:23.554Z | v1.12.6 281466 | load_balancing_call | [2] Pick called
D 2025-02-24T05:51:23.555Z | v1.12.6 281466 | load_balancing_call | [2] Pick result: COMPLETE subchannel: (1) 193.25.200.73:443 status: undefined undefined
D 2025-02-24T05:51:23.555Z | v1.12.6 281466 | connectivity_state | (1) dns:console.meteoscientific.com:443 CONNECTING -> READY
D 2025-02-24T05:51:23.556Z | v1.12.6 281466 | transport_flowctrl | (1) 193.25.200.73:443 local window size: 65535 remote window size: 65535
D 2025-02-24T05:51:23.556Z | v1.12.6 281466 | transport_internals | (1) 193.25.200.73:443 session.closed=false session.destroyed=false session.socket.destroyed=false
D 2025-02-24T05:51:23.557Z | v1.12.6 281466 | load_balancing_call | [2] Created child call [3]
D 2025-02-24T05:51:23.557Z | v1.12.6 281466 | subchannel_call | [3] write() called with message of length 5
D 2025-02-24T05:51:23.557Z | v1.12.6 281466 | subchannel_call | [3] sending data chunk of length 5
D 2025-02-24T05:51:23.558Z | v1.12.6 281466 | load_balancing_call | [2] halfClose called
D 2025-02-24T05:51:23.558Z | v1.12.6 281466 | subchannel_call | [3] end() called
D 2025-02-24T05:51:23.558Z | v1.12.6 281466 | subchannel_call | [3] calling end() on HTTP/2 stream
D 2025-02-24T05:51:24.274Z | v1.12.6 281466 | transport | (1) 193.25.200.73:443 local settings acknowledged by remote: {"headerTableSize":4096,"enablePush":true,"initialWindowSize":65535,"maxFrameSize":16384,"maxConcurrentStreams":4294967295,"maxHeaderListSize":4294967295,"maxHeaderSize":4294967295,"enableConnectProtocol":false}
D 2025-02-24T05:51:24.276Z | v1.12.6 281466 | subchannel_call | [3] Received server headers:
:status: 400
server: nginx/1.27.3
date: Mon, 24 Feb 2025 05:51:24 GMT
content-length: 0
D 2025-02-24T05:51:24.276Z | v1.12.6 281466 | subchannel_call | [3] Received server trailers:
:status: 400
server: nginx/1.27.3
date: Mon, 24 Feb 2025 05:51:24 GMT
content-length: 0
D 2025-02-24T05:51:24.277Z | v1.12.6 281466 | subchannel_call | [3] ended with status: code=13 details="Received HTTP status code 400"
D 2025-02-24T05:51:24.277Z | v1.12.6 281466 | load_balancing_call | [2] Received status
D 2025-02-24T05:51:24.277Z | v1.12.6 281466 | load_balancing_call | [2] ended with status: code=13 details="Received HTTP status code 400" start time=2025-02-24T05:51:23.200Z
D 2025-02-24T05:51:24.277Z | v1.12.6 281466 | retrying_call | [1] Received status from child [2]
D 2025-02-24T05:51:24.278Z | v1.12.6 281466 | retrying_call | [1] state=TRANSPARENT_ONLY handling status with progress PROCESSED from child [2] in state ACTIVE
D 2025-02-24T05:51:24.278Z | v1.12.6 281466 | retrying_call | [1] ended with status: code=13 details="Received HTTP status code 400" start time=2025-02-24T05:51:23.200Z
D 2025-02-24T05:51:24.278Z | v1.12.6 281466 | resolving_call | [0] Received status
D 2025-02-24T05:51:24.278Z | v1.12.6 281466 | resolving_call | [0] ended with status: code=13 details="Received HTTP status code 400"
Error: 13 INTERNAL: Received HTTP status code 400
at callErrorFromStatus (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/call.js:32:19)
at Object.onReceiveStatus (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/client.js:193:76)
at Object.onReceiveStatus (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:361:141)
at Object.onReceiveStatus (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:324:181)
at /home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/resolving-call.js:129:78
at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
for call at
at ServiceClientImpl.makeUnaryRequest (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/client.js:161:32)
at ServiceClientImpl.list (/home/tve/Projects/Helium/chirp-api/node_modules/@grpc/grpc-js/build/src/make-client.js:105:19)
at file:///home/tve/Projects/Helium/chirp-api/add-device.mjs:47:15
at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:336:24)
at async loadESM (node:internal/process/esm_loader:34:7)
at async handleMainPromise (node:internal/modules/run_main:106:12) {
code: 13,
details: 'Received HTTP status code 400',
metadata: Metadata {
internalRepr: Map(3) {
'server' => [Array],
'date' => [Array],
'content-length' => [Array]
},
options: {}
}
}
D 2025-02-24T05:51:24.281Z | v1.12.6 281466 | subchannel_call | [3] HTTP/2 stream closed with code 0