api: Unable to Generate Token using api Package due to Schema Mismatch

I have been attempting to integrate with the Basiq API (v3) by following the documentation provided at https://api.basiq.io/reference/posttoken. My goal is to generate a new token. While I was successful in achieving this using tools like Postman and curl, I encountered consistent errors when trying to implement the same functionality using the provided Node.js example with the api package.

The primary issue is that regardless of the data I provided in the request body, I received errors indicating that the body was missing. This is perplexing since I ensured that the request body was formatted according to the documentation.

Upon closer investigation, I discovered that the issue lies within the api package’s implementation, specifically in the prepareParams function located at https://github.com/readmeio/api/blob/main/packages/api/src/core/prepareParams.ts#L269. This function expects a schema entry with the type body. However, for the /token endpoint, the content type is set to application/x-www-form-urlencoded, resulting in the schema being generated with formData instead of body. Consequently, the body information is being removed from the request.

Interestingly, this problem seems to be isolated to the /token endpoint. Other endpoints function as expected, using the application/json content type and interacting well with the api package.

Steps to Reproduce:

Attempt to generate a new token using the provided Node.js example and the api package for the /token endpoint. Observe that the request fails, with errors indicating that the request body is missing.

Expected Behavior:

The api package should handle requests to the /token endpoint seamlessly, despite the differing content type of application/x-www-form-urlencoded.

Possible Workarounds:

Since the api package seems to struggle with the formData schema of the /token endpoint, you might consider the following workarounds:

Directly using axios or node-fetch: Bypass the api package and make requests to the /token endpoint using a library like axios or node-fetch.

Adapting the api Package: Modify the api package’s prepareParams function to accommodate the formData schema in the /token endpoint. This might involve altering how the function interprets the schema type or enhancing its flexibility.

Impact:

This issue prevents developers from effectively using the provided api package for token generation using the /token endpoint. It might lead to frustration and confusion when attempting to integrate with the Basiq API.

Additional Information:

Basiq API Documentation: https://api.basiq.io/reference/posttoken Affected Endpoint: /token Affected Package: api (https://github.com/readmeio/api)

Please let me know if you need any further information or clarification on this issue.

OPENAPI Spec: https://raw.githubusercontent.com/basiqio/api-ref/main/core.yml

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 16 (8 by maintainers)

Most upvoted comments

👋 These fixes have landed in v6.1.1. https://github.com/readmeio/api/releases/tag/v6.1.1

The updates to code snippets in your docs will be landing in Monday mornings release.

You’re welcome! 🎉 Thank you for your commitment to making improvements and fixes. Your dedication to addressing the issues, including the duplicate header issue and the enhancement for handling reserved headers, is greatly appreciated. We look forward to the upcoming updates and the improved functionality they will bring.

Your efforts in collaborating and your patience are commendable. Keep up the great work! 👍😊

🎉 Will get some fixes going for yall. I can likely get the api fix which will include that dupe header issue and automatically removing reserved headers (like accept) from body payloads published to NPM sometime tomorrow. To remove the accept header entirely from the code snippets on your docs will likely land in Monday mornings release as we deploy to prod once a day.

Thanks for your patience and the semi-pair programming sesh!

I was still using the old node_modules with this version edsvz2rlk7p46u2.

After checking it for a while, Here’s what I have done to fix.

rm -rf node_modules
npm install api --save

Thanks.

Without any fixes if you do this it works:

const sdk = require('api')('@basiq/v3.0#edsvz2rlk7p46u2');

sdk.auth('Basic NjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1OjQ3NWYwMzhkLTBlZmItNGM1ZS1iMzQ0LTAzMzYxOTkyYTRlMw==');
sdk.postToken({scope: 'SERVER_ACCESS'}, {
  // accept: 'application/json',
  'BASIQ-version': '3.0'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));
{
  access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0bmVyaWQiOiJkMTRjYzU5Zi0yM2UzLTQ4NjEtOGJiMi1mZWMwZDcxMWMxMjkiLCJhcHBsaWNhdGlvbmlkIjoiNjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1Iiwic2NvcGUiOiJTRVJWRVJfQUNDRVNTIiwic2FuZGJveF9hY2NvdW50Ijp0cnVlLCJjb25uZWN0X3N0YXRlbWVudHMiOmZhbHNlLCJlbnJpY2giOiJkaXNhYmxlZCIsImVucmljaF9lbnRpdHkiOmZhbHNlLCJlbnJpY2hfbG9jYXRpb24iOmZhbHNlLCJlbnJpY2hfY2F0ZWdvcnkiOmZhbHNlLCJhZmZvcmRhYmlsaXR5Ijoic2FuZGJveCIsImluY29tZSI6InNhbmRib3giLCJleHBlbnNlcyI6InNhbmRib3giLCJleHAiOjE2OTIzMjI5MjIsImlhdCI6MTY5MjMxOTMyMiwidmVyc2lvbiI6IjMuMCIsImRlbmllZF9wZXJtaXNzaW9ucyI6W119.MVuqMqAKw04fmeqnHgSF3UdrH88rk01q-meiTZktADY',
  token_type: 'Bearer',
  expires_in: 3600
}

Just saw your edit as I was typing it out. This snippet works for me with and without the dupe header fix I’ve got locally:

const sdk = require('api')('@basiq/v3.0#edsvz2rlk7p46u2');

sdk.auth('Basic NjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1OjQ3NWYwMzhkLTBlZmItNGM1ZS1iMzQ0LTAzMzYxOTkyYTRlMw==');
sdk.postToken({ scope: 'CLIENT_ACCESS', userId: 'userid' }, {
  'BASIQ-version': '3.0',
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));
{
  access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0bmVyaWQiOiJkMTRjYzU5Zi0yM2UzLTQ4NjEtOGJiMi1mZWMwZDcxMWMxMjkiLCJhcHBsaWNhdGlvbmlkIjoiNjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1IiwidXNlcmlkIjoidXNlcmlkIiwic2NvcGUiOiJDTElFTlRfQUNDRVNTIiwic2FuZGJveF9hY2NvdW50Ijp0cnVlLCJjb25uZWN0X3N0YXRlbWVudHMiOmZhbHNlLCJlbnJpY2giOiJkaXNhYmxlZCIsImVucmljaF9lbnRpdHkiOmZhbHNlLCJlbnJpY2hfbG9jYXRpb24iOmZhbHNlLCJlbnJpY2hfY2F0ZWdvcnkiOmZhbHNlLCJhZmZvcmRhYmlsaXR5Ijoic2FuZGJveCIsImluY29tZSI6InNhbmRib3giLCJleHBlbnNlcyI6InNhbmRib3giLCJleHAiOjE2OTIzMjIzMTUsImlhdCI6MTY5MjMxODcxNSwidmVyc2lvbiI6IjMuMCIsImRlbmllZF9wZXJtaXNzaW9ucyI6W119.wY1CZyfobB8cy-jux0hV0755UAaElLkxH6XmZAlHdpk',
  token_type: 'Bearer',
  expires_in: 3600
}

Running that SERVER_ACCESS one I now see the “Invalid request body” error.

const sdk = require('api')('@basiq/v3.0#edsvz2rlk7p46u2');

sdk.auth('Basic NjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1OjQ3NWYwMzhkLTBlZmItNGM1ZS1iMzQ0LTAzMzYxOTkyYTRlMw==');
sdk.postToken({scope: 'SERVER_ACCESS'}, {
  accept: 'application/json',
  'BASIQ-version': '3.0'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));
{
  type: 'list',
  correlationId: '0e55c6d8-ab97-4182-b783-ca91014a8795',
  data: [
    {
      type: 'error',
      code: 'parameter-not-valid',
      title: 'Required parameter not valid',
      detail: 'Invalid request body',
      source: { pointer: 'body', parameter: 'body' }
    }
  ]
}

The HAR that we’re generating internally to make this request is the follwing:

{
  log: {
    entries: [
      {
        request: {
          cookies: [],
          headers: [
            { name: 'accept', value: 'application/json' },
            { name: 'basiq-version', value: '3.0' },
            {
              name: 'content-type',
              value: 'application/x-www-form-urlencoded'
            },
            {
              name: 'Authorization',
              value: 'Basic NjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1OjQ3NWYwMzhkLTBlZmItNGM1ZS1iMzQ0LTAzMzYxOTkyYTRlMw=='
            }
          ],
          headersSize: 0,
          queryString: [],
          postData: {
            params: [
              { name: 'scope', value: 'SERVER_ACCESS' },
              { name: 'accept', value: 'application/json' }
            ],
            mimeType: 'application/x-www-form-urlencoded'
          },
          bodySize: 0,
          method: 'POST',
          url: 'https://au-api.basiq.io/token',
          httpVersion: 'HTTP/1.1'
        }
      }
    ]
  }
}

The problem appears to be that we’re sending that danged accept header in the body payload as feeding that HAR into our fetch-har library that api here uses with and without that in the params array makes it clear that that’s the problem:

require('isomorphic-fetch');

const fetchHAR = require('fetch-har').default;

const har = {
  log: {
    entries: [
      {
        request: {
          cookies: [],
          headers: [
            { name: 'accept', value: 'application/json' },
            { name: 'basiq-version', value: '3.0' },
            {
              name: 'content-type',
              value: 'application/x-www-form-urlencoded',
            },
            {
              name: 'Authorization',
              value:
                'Basic NjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1OjQ3NWYwMzhkLTBlZmItNGM1ZS1iMzQ0LTAzMzYxOTkyYTRlMw==',
            },
          ],
          headersSize: 0,
          queryString: [],
          postData: {
            params: [
              { name: 'scope', value: 'SERVER_ACCESS' },
              // { name: 'accept', value: 'application/json' },
            ],
            mimeType: 'application/x-www-form-urlencoded',
          },
          bodySize: 0,
          method: 'POST',
          url: 'https://au-api.basiq.io/token',
          httpVersion: 'HTTP/1.1',
        },
      },
    ],
  },
};

fetchHAR(har)
  .then(res => res.json())
  .then(console.log);
{
  access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0bmVyaWQiOiJkMTRjYzU5Zi0yM2UzLTQ4NjEtOGJiMi1mZWMwZDcxMWMxMjkiLCJhcHBsaWNhdGlvbmlkIjoiNjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1Iiwic2NvcGUiOiJTRVJWRVJfQUNDRVNTIiwic2FuZGJveF9hY2NvdW50Ijp0cnVlLCJjb25uZWN0X3N0YXRlbWVudHMiOmZhbHNlLCJlbnJpY2giOiJkaXNhYmxlZCIsImVucmljaF9lbnRpdHkiOmZhbHNlLCJlbnJpY2hfbG9jYXRpb24iOmZhbHNlLCJlbnJpY2hfY2F0ZWdvcnkiOmZhbHNlLCJhZmZvcmRhYmlsaXR5Ijoic2FuZGJveCIsImluY29tZSI6InNhbmRib3giLCJleHBlbnNlcyI6InNhbmRib3giLCJleHAiOjE2OTIzMjI2MjYsImlhdCI6MTY5MjMxOTAyNiwidmVyc2lvbiI6IjMuMCIsImRlbmllZF9wZXJtaXNzaW9ucyI6W119.ryt5SsWikjviUxM9sv4OF7pzAPoy6CO-_dh5aALJ6ec',
  token_type: 'Bearer',
  expires_in: 3600
}

Working on a fix!

Brilliant, thank you so much.

Hey @erunion this isn’t actually working for me! I am still getting 400 error. Attaching the code for you as well to retry.

const sdk = require('api')('@basiq/v3.0#edsvz2rlk7p46u2');

sdk.auth('Basic NjMxMjNmMWMtZjYxMy00ZjMyLWFiYzUtYzBhZDdhYTY2YmU1OjQ3NWYwMzhkLTBlZmItNGM1ZS1iMzQ0LTAzMzYxOTkyYTRlMw==');
sdk.postToken({scope: 'SERVER_ACCESS'}, {
  accept: 'application/json',
  'BASIQ-version': '3.0'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));

This is a dummy API key and only used for testing purposes.

I actually just found the problem! When we compile headers that are specified in the metadata parameter of api if the casing of the parameter matched the casing of the header in the OpenAPI definition we weren’t retaining that casing name to later remove it from the body, resulting in the header getting duped.

So like if you did this it would work:

const sdk = require('api')('@basiq/v3.0#edsvz2rlk7p46u2');

sdk.auth('Basic someAuthToken');
sdk.postToken({scope: 'scope', userId: 'userid'}, {
  accept: 'application/json',
  'BASIQ-version': '3.0'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));

Can you confirm if you miscase the basiq-version header like that that it works?

Assuming that does it I’m going to fix this unnecessary accept header in the snippet and then get a PR going.

Thanks for the detailed report! I see we’ve already got this same issue in our internal ticketing system from a couple week ago[^1].

Looking into digestParameters I don’t believe that param.in will ever equal body because in: body can’t be done in OpenAPI 3.x anymore and the definition in question is OpenAPI 3.0.1. api also doesn’t support Swagger definitions either.

const sdk = require('api')('@basiq/v3.0#edsvz2rlk7p46u2');

sdk.auth('Basic someAuthToken');
sdk.postToken({scope: 'scope', userId: 'userid'}, {
  accept: 'application/json',
  'basiq-version': '3.0'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));

Looking at this code snippet and dumping what prepareParams is generating I see that we’re trying to send this basiq-version header in the header and the formData payload. The accept header is also being sent in the payload as well which is very strange – though accept shouldn’t even exist in the code snippet to begin with because api handles that already.

{
  formData: {
    scope: 'scope',
    userId: 'userid',
    accept: 'application/json',
    'basiq-version': '3.0'
  },
  header: { 'basiq-version': '3.0' }
}

Seeing what I can do now to fix this.

[^1]: I’ve been unfortunately very backlogged this month.