'API call to crypto.com in node returns invalid JSON response

I'm sending a POST request to the crypto.com public API (reference) using node-fetch. More specifically, I'm am attempting to call the private method get-account-summary and I am signing the request beforehand with my API Key and my Secret Key as prescribed on their API reference page (see digital signature).

const requestBody = JSON.stringify(signRequest(request, apiKey, apiSecret));

fetch('https://api.crypto.com/v2/private/get-account-summary', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'}, 
  body: requestBody
})
.then(response => response.body)
.then(data => {
  console.log('Success:', data);
})
.catch((error) => {
  console.error('Error:', error);
});

The request is apparently successful, but I'm struggling to make sense of the API response which is supposed to look like this:

{
    "id": 11,
    "method": "private/get-account-summary",
    "code": 0,
    "result": {
        "accounts": [
            {
                "balance": 99999999.905000000000000000,
                "available": 99999996.905000000000000000,
                "order": 3.000000000000000000,
                "stake": 0,
                "currency": "CRO"
            }
        ]
    }
}

but this is what I actually get:

Success: PassThrough {
  _readableState: ReadableState {
    objectMode: false,
    highWaterMark: 16384,
    buffer: BufferList { head: null, tail: null, length: 0 },
    length: 0,
    pipes: [],
    flowing: null,
    ended: true,
    endEmitted: false,
    reading: false,
    constructed: true,
    sync: false,
    needReadable: false,
    emittedReadable: false,
    readableListening: false,
    resumeScheduled: false,
    errorEmitted: false,
    emitClose: true,
    autoDestroy: true,
    destroyed: false,
    errored: null,
    closed: false,
    closeEmitted: false,
    defaultEncoding: 'utf8',
    awaitDrainWriters: null,
    multiAwaitDrain: false,
    readingMore: false,
    dataEmitted: false,
    decoder: null,
    encoding: null,
    [Symbol(kPaused)]: null
  },
  _events: [Object: null prototype] {
    prefinish: [Function: prefinish],
    error: [Function (anonymous)]
  },
  _eventsCount: 2,
  _maxListeners: undefined,
  _writableState: WritableState {
    objectMode: false,
    highWaterMark: 16384,
    finalCalled: true,
    needDrain: false,
    ending: true,
    ended: true,
    finished: true,
    destroyed: false,
    decodeStrings: true,
    defaultEncoding: 'utf8',
    length: 0,
    writing: false,
    corked: 0,
    sync: false,
    bufferProcessing: false,
    onwrite: [Function: bound onwrite],
    writecb: null,
    writelen: 0,
    afterWriteTickInfo: null,
    buffered: [],
    bufferedIndex: 0,
    allBuffers: true,
    allNoop: true,
    pendingcb: 0,
    constructed: true,
    prefinished: true,
    errorEmitted: false,
    emitClose: true,
    autoDestroy: true,
    errored: null,
    closed: false,
    closeEmitted: false,
    [Symbol(kOnFinished)]: []
  },
  allowHalfOpen: true,
  [Symbol(kCapture)]: false,
  [Symbol(kCallback)]: null
}

You may have noticed that I logged the response.body to the console rather than the response.json() because whenever I call the latter, I get an "invalid json error"

Error: FetchError: invalid json response body at
https://api.crypto.com/v2/private/get-account-summary reason:
Unexpected end of JSON input
    at C:\Users\...\app\node_modules\node-fetch\lib\index.js:273:32
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  type: 'invalid-json' 


Solution 1:[1]

FYI this issue had nothing to do with the way in which the API request was made, it was related to the signing of the request body instead (which is not mentioned in the question); the signRequest(...) method that is passed as an argument to the fetch method after being stringified, does not actually return any values but instead mutates the request argument. Once I accounted for this, the API call was successful.

Solution 2:[2]

Many API stacks work in terms of 2 promises:

  • Make the remote request
  • Deserialize the response

According to docs you need to await the .json() call.

import fetch from 'node-fetch';

const response = await fetch('https://httpbin.org/post', {
    method: 'post',
    body: JSON.stringify(body),
    headers: {'Content-Type': 'application/json'}
});
const data = await response.json();

Nested promises looks messier when using then, which is why many people prefer the async await syntax, but this will work:

request('https://api.crypto.com/v2/private/get-account-summary', {
  json: true,
  method: 'POST',
  headers: {'Content-Type': 'application/json'}, 
  body: requestBody
})
.then(response => {
   response.json()
       .then(data => {
            console.log(data);
        })
})

Solution 3:[3]

Fetch returns a response stream. A simpler approach would be to use npm request package.

Ex :

const request = require('request');
const requestBody = JSON.stringify(signRequest(request, apiKey, apiSecret));

request('https://api.crypto.com/v2/private/get-account-summary', {
  json: true,
  method: 'POST',
  headers: {'Content-Type': 'application/json'}, 
  body: requestBody
})
.then(response => response.body)
.then(data => {
  console.log('Success:', data);
})
.catch((error) => {
  console.error('Error:', error);
});

Solution 4:[4]

EDIT: Sorry I missed the end of the question. If json() is invalid and the text() returns nothing, I would say you should check your request body. The request is successful, but you get an empty response.

Not sure why you are using response.body. You need to use json() if you want the response in a JSON format. This will take the body and return a promise which resolve to the requested format. You can read it here.

So, your code should look like this:

fetch('https://api.crypto.com/v2/private/get-account-summary', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'}, 
  body: requestBody
})
.then(response => response.json())
.then(data => {
  console.log('Success:', data);
})
.catch((error) => {
  console.error('Error:', error);
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Aaronv
Solution 2 Gary Archer
Solution 3 Rohìt Jíndal
Solution 4