Hey ChirpStack community,
After many hours of debugging the REST API downlink enqueue (v4.13.0, chirpstack-rest-api on port 8090), I want to share the correct way to do it with cURL — and point out two misleading parts in the docs/Swagger that cost me a lot of time.
The Swagger UI shows:
"data": "string"
and for f_port:
"fPort": 1
Both are wrong or misleading!
1. data must be Base64 of raw bytes — not text or JSON
If you send:
"data": "{\"hex\":\"03de011401000500000000\"}"
→ Device gets the JSON string, not your binary payload.
Correct: Convert hex → raw bytes → Base64
Example:
hex: 03de011401000500000000
→ Base64: A94BFAEABQAAAAAAA==
2. f_port must be snake_case, not fPort
Swagger shows fPort, but the Protobuf schema uses:
uint32 f_port = 2;
So use f_port, not fPort!
Correct cURL Command
curl -X POST "http://YOUR_HOST:8090/api/devices/YOUR_DEVEUI/queue" \
-H "accept: application/json" \
-H "Grpc-Metadata-Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"queueItem": {
"confirmed": false,
"f_port": 7,
"data": "A94BFAEABQAAAAAAA=="
}
}'
Result:
- UI shows:
03DE011401000500000000(all zeros preserved!) - Device receives: raw bytes
03 de 01 14 01 00 05 00 00 00 00
JS Snippet: Hex → Base64 (Browser – no Buffer)
function hexToBase64Browser(hex) {
hex = hex.replace(/\s/g, '').toUpperCase();
if (!/^[0-9A-F]+$/i.test(hex) || hex.length % 2 !== 0) throw new Error("Invalid hex");
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
}
let binary = '';
bytes.forEach(b => binary += String.fromCharCode(b));
return btoa(binary);
}
console.log(hexToBase64Browser("03de011401000500000000")); // A94BFAEABQAAAAAAA==
JS Snippet: Hex → Base64 (Node.js Server-Side)
function hexToBase64Node(hex) {
return Buffer.from(hex.replace(/\s/g, ''), 'hex').toString('base64');
}
console.log(hexToBase64Node("03de011401000500000000")); // A94BFAEABQAAAAAAA==
Docs Suggestion
Update Swagger example:
"data": "A94BFAEABQAAAAAAA==" // Base64 of hex 03de011401000500000000
and use "f_port": 7 — not fPort.
Question to the team:
Why was the decision made to use Protobuf field names (f_port, bytes data) directly in the REST API JSON, instead of a more REST-friendly format (like fPort, plain hex, or JSON)? It breaks Swagger usability and confuses users.
Thanks for any insight!
Jan from ShopOfThings.ch