Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Calling the Jira server search API with Postman returns data, but I get a 400 error through my API

Justin Ledoux January 24, 2019

For some reason, I am able to call the Jira Server search API and get data back using Postman, but that exact same call on my Express API returns the following error:

400 - {"errorMessages":["The value 'RCA' does not exist for the field 'project'."],"errors":{}}

RCA does in fact exist and again, it works with Postman.

In both cases, I send a POST with the following JSON payload:

{ jql: 'project = RCA' }

I'm also passing the JSESSIONID header with the token returned by the auth API.

So I'm at a loss here... I have no idea why one it working and the other isn't. 

3 answers

1 accepted

0 votes
Answer accepted
Justin Ledoux January 28, 2019

The Jira Server API documentation currently suggests using request headers or Oauth tokens to maintain a session. But that is completely misleading and wrong.

The API requires the presence of a JSESSIONID cookie with the session token that is returned by the API.

Here is an example of my working code:

const request = require('request-promise-native');

exports.syncIssues = (req, res) => {
// Create a cookie jar using request.jar API
const
jar = request.jar();

// I call the session API and store the returned object
// on the browser side cookies (jira-login). In every single
// other calls, I parse that cookie to include it in a cookie
// jar inside the request options.
const jiraCookie = JSON.parse(cookies['jira-login']).value;

// I create the cookie using the request.cookie API
const cookie = request.cookie(`JSESSIONID=${jiraCookie}`);
const url = 'https://<jira endpoint here>';

// I set the cookie in the cookie jar
jar
.setCookie(cookie, url);

const options = {
method: 'POST',
uri: 'https://<jira endpoint here>/rest/api/2/search',
body: {
jql: 'updated <= now()',
startAt: 0,
maxResults: 1,
},
json: true,

// Add the jar to the request options.
jar,
};

request
(options)
.then((body) => {
res.json(body);
})
.catch((err) => {
res.status(err.statusCode).json(err);
});
};
0 votes
Justin Ledoux January 28, 2019

@Thomas Deiler, I was able to find a solution. It actually had nothing to do with angular, server configs, etc.

I believe the Jira API documentation is very misleading (or plain wrong). Everything I read about the Authentication API mentioned 2 ways of maintaining a session: Through request headers or through an Oauth token.

Because using headers was the simplest implementation, I went with that, but it turns out that the Jira API does NOT use the JSESSIONID header what so ever. It uses cookies.

The reason it worked with Postman, is that Postman saves cookies returned/set by the API much like a browser would. And so it would submit the cookies along with the calls.

That said, my ExpressJS API would only send headers as the Jira documentation  suggested. After fiddling around with many other ideas, I figured out that all I needed to do it send the JSESSIONID cookie along every call and it worked fine.

Here is a snippet of my NodeJS, ExpressJS code to help others:

const request = require('request-promise-native');

exports.syncIssues = (req, res) => {
// Create a cookie jar using request.jar API
const
jar = request.jar();

// I call the session API and store the returned object
// on the browser side cookies (jira-login). In every single
// other calls, I parse that cookie to include it in a cookie
// jar inside the request options.
const jiraCookie = JSON.parse(cookies['jira-login']).value;

// I create the cookie using the request.cookie API
const cookie = request.cookie(`JSESSIONID=${jiraCookie}`);
const url = 'https://<jira endpoint here>';

// I set the cookie in the cookie jar
jar
.setCookie(cookie, url);

const options = {
method: 'POST',
uri: 'https://<jira endpoint here>/rest/api/2/search',
body: {
jql: 'updated <= now()',
startAt: 0,
maxResults: 1,
},
json: true,

// Add the jar to the request options.
jar,
};

request
(options)
.then((body) => {
res.json(body);
})
.catch((err) => {
res.status(err.statusCode).json(err);
});
};
0 votes
Thomas Deiler
Community Champion
January 24, 2019

Dear @Justin Ledoux,

have you tried to seach with a GET request instead of the POST?

So long

Thomas

Justin Ledoux January 24, 2019

Yes I have and I get exactly the same issue.

Thomas Deiler
Community Champion
January 24, 2019

What is Express API? Can you increase the verbosity level? What's the error message, when you search for a project, that doesn't exist?

If you are familiar with wireshark you could catch both request on network level and compare. I doubt, that they are similar ...

Justin Ledoux January 24, 2019

I'm building an internal Angular / NodeJS app for reporting that will pull in data from many data sources. ExpressJS is the framework that makes it dead easy to create APIs on NodeJS.

I can give you the whole response I get from Jira, but it is quite a lot of bullshit text (and I believe contains some ExpressJS specific data as well).

Here is it.

What is returned to the browser by my API (and you'll see it's got some Angular specific junk in there which is normal)

{
  "data": null,
  "status": -1,
  "config": {
    "method": "POST",
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "jsonpCallbackParam": "callback",
    "url": "\/api\/jira\/search",
    "headers": {
      "JSESSIONID": "AFF1F52ABA75217C577382C923C07FE4",
      "Accept": "application\/json, text\/plain, *\/*",
      "Cache-Control": "no-cache",
      "Content-Type": "application\/json;charset=utf-8"
    },
    "data": {
      "jql": "project = RCA",
      "startAt": 1490,
      "fields": [
        "timespent"
      ]
    }
  },
  "statusText": "",
  "xhrStatus": "abort"
}

And here is the same error but from the API:

{
  "name": "StatusCodeError",
  "statusCode": 400,
  "message": "400 - {\"errorMessages\":[\"The value 'RCA' does not exist for the field 'project'.\"],\"errors\":{}}",
  "error": {
    "errorMessages": [
      "The value 'RCA' does not exist for the field 'project'."
    ],
    "errors": {
      
    }
  },
  "options": {
    "method": "POST",
    "uri": "<hiding the jira host>\/rest\/api\/2\/search",
    "body": {
      "jql": "project = RCA",
      "startAt": 1490,
      "fields": [
        "timespent"
      ]
    },
    "headers": {
      "JSESSIONID": "AFF1F52ABA75217C577382C923C07FE4"
    },
    "json": true,
    "resolveWithFullResponse": true,
    "simple": true,
    "transform2xxOnly": false
  },
  "response": {
    "statusCode": 400,
    "body": {
      "errorMessages": [
        "The value 'RCA' does not exist for the field 'project'."
      ],
      "errors": {
        
      }
    },
    "headers": {
      "date": "Thu, 24 Jan 2019 16:59:56 GMT",
      "content-type": "application\/json;charset=UTF-8",
      "transfer-encoding": "chunked",
      "connection": "close",
      "server": "nginx\/1.10.3 (Ubuntu)",
      "x-arequestid": "1019x549174x1",
      "x-xss-protection": "1; mode=block",
      "x-content-type-options": "nosniff",
      "x-frame-options": "SAMEORIGIN",
      "content-security-policy": "frame-ancestors 'self'",
      "x-asen": "SEN-10463924",
      "set-cookie": [
        "atlassian.xsrf.token=BKB6-5CSC-155L-OS04_dbc3e26ad7629d2d7961bcdc802f5bd50d3a1d2d_lout;path=\/;Secure"
      ],
      "x-ausername": "anonymous",
      "cache-control": "no-cache, no-store, no-transform"
    },
    "request": {
      "uri": {
        "protocol": "https:",
        "slashes": true,
        "auth": null,
        "host": "<hiding the jira host>",
        "port": 443,
        "hostname": "<hiding the jira host>",
        "hash": null,
        "search": null,
        "query": null,
        "pathname": "\/rest\/api\/2\/search",
        "path": "\/rest\/api\/2\/search",
        "href": "<hiding the jira host>\/rest\/api\/2\/search"
      },
      "method": "POST",
      "headers": {
        "JSESSIONID": "AFF1F52ABA75217C577382C923C07FE4",
        "accept": "application\/json",
        "content-type": "application\/json",
        "content-length": 61
      }
    }
  }
}
Justin Ledoux January 24, 2019

Here is the successful response from Postman:

Screen Shot 2019-01-24 at 12.04.11 PM.png

Thomas Deiler
Community Champion
January 25, 2019

But what you send with Postman as payload ist not the same you send with your Angular app. What are the two headers like displayed on your screenshot?

Btw: the upper json search request seems much to complicated. I recommend simple using a: 

GET /search?jql=project=RCA

And if you make usage of POST cross-site referencing could be an issue. Please read this article.

Justin Ledoux January 25, 2019

The problem and response is the same. I just copied the wrong one. 

Justin Ledoux January 25, 2019

And in this case, it isn't the browser that is calling Jira directly. The browser is calling an internal API, and that API calls Jira and does get a response. But a response that the queried project does not exist.

Thomas Deiler
Community Champion
January 25, 2019

Dear @Justin Ledoux,

I know how exhausting development against the rest api can be. I did in Java with Apaches HTMLClient and I got gray hairs while doing it. Anyhow in your situation I would point out following topics for the source of the problem:

  • Angular Implementation
  • Jira server configuration (reverse proxy, load balancer, netfilter ...)
  • Client Operation System issue

For the first, I recommend checking out your API implementation against something completely different. For example code against http://www.jsontest.com/ just to ensure the request are built in the right way.

The second one can be verified by analyzing the network traffic. First increase verbosity log level on Jira side an compare the entries between Postman and Angular. One level deeper, use wireshark.

The third, easiest to proof. Just repeat the requests form another client.

The REST API of Jira itself will not be the root cause. It is available for a very long time and has convince with its stability. This is valid for Jira 7.12. or greater. In earlier versions beyond V6 this is probably not the case.

Let me know how I can help you further.

So long

Thomas

Justin Ledoux January 28, 2019

@Thomas Deiler, I was able to find a solution. It actually had nothing to do with angular, server configs, etc.

I believe the Jira API documentation is very misleading (or plain wrong). Everything I read about the Authentication API mentioned 2 ways of maintaining a session: Through request headers or through an Oauth token.

Because using headers was the simplest implementation, I went with that, but it turns out that the Jira API does NOT use the JSESSIONID header what so ever. It uses cookies.

The reason it worked with Postman, is that Postman saves cookies returned/set by the API much like a browser would. And so it would submit the cookies along with the calls.

That said, my ExpressJS API would only send headers as the Jira documentation  suggested. After fiddling around with many other ideas, I figured out that all I needed to do it send the JSESSIONID cookie along every call and it worked fine.

Here is a snippet of my NodeJS, ExpressJS code to help others:

const request = require('request-promise-native');

exports.syncIssues = (req, res) => {
// Create a cookie jar using request.jar API
const
jar = request.jar();

// I call the session API and store the returned object
// on the browser side cookies (jira-login). In every single
// other calls, I parse that cookie to include it in a cookie
// jar inside the request options.
const jiraCookie = JSON.parse(cookies['jira-login']).value;

// I create the cookie using the request.cookie API
const cookie = request.cookie(`JSESSIONID=${jiraCookie}`);
const url = 'https://<jira endpoint here>';

// I set the cookie in the cookie jar
jar
.setCookie(cookie, url);

const options = {
method: 'POST',
uri: 'https://<jira endpoint here>/rest/api/2/search',
body: {
jql: 'updated <= now()',
startAt: 0,
maxResults: 1,
},
json: true,

// Add the jar to the request options.
jar,
};

request
(options)
.then((body) => {
res.json(body);
})
.catch((err) => {
res.status(err.statusCode).json(err);
});
};

Suggest an answer

Log in or Sign up to answer