Brett DeWoody
Brett DeWoody

Reputation: 62881

Retrieve then POST a photo to a Foursquare Checkin with Axios

I'm trying to retrieve, then POST a JPEG image to Foursquare's https://api.foursquare.com/v2/photos/add endpoint using Axios in Node. I've tried a few methods with Axios (and Postman) but always receive the same error response of Missing file upload:

{
  "meta": {
    "code": 400,
    "errorType": "other",
    "errorDetail": "Missing file upload",
    "requestId": "NNNNNNNNNNNNNNNNNNNNN"  // not the true requestId
  },
  "notifications": [
    {
      "type": "notificationTray",
      "item": {
        "unreadCount": 0
      }
    }
  ],
  "response": {}
}

The image is created using the Google Static Map API and retrieved with an Axios GET request:

const image = await axios.get(imageURL, {
  responseType: "arraybuffer"
});

which is wrapped in an async function and successfully returns a buffer. The data is read into a Buffer and converted to a string:

const imageData = new Buffer(image, "binary").toString();

Here's an example imageData string. I've also tried converting the string to base64.

This string is then POSTed to the Foursquare endpoint:

const postPhoto = await axios.post(
  "https://developer.foursquare.com/docs/api/photos/add?
    checkinId=1234&
    oauth_token=[TOKEN]&
    v=YYYYMMDD",
  imageData,
  {
    headers: { "Content-Type": "image/jpeg" }
  }
);

where the checkinId, oauth_token and v params are all valid.

I've tried different Content-Type values, base64 encoding the imageData and several other solutions found in forums and here on SO (most are several years old), but nothing works. The response errorDetail always says Missing file upload.

The issue could be in how the POST request is structured, but I could also be requesting/handling the image data incorrectly. A 2nd (or 3rd, or 4th) set of eyes to check I'm putting this together would be super helpful.

Upvotes: 1

Views: 404

Answers (1)

Brett DeWoody
Brett DeWoody

Reputation: 62881

Whew, I have finally solved this.

I was eventually able to get it working thru Postman which provided some hints. Here's the Postman code snippet using request:

var fs = require("fs");
var request = require("request");

var options = { method: 'POST',
  url: 'https://api.foursquare.com/v2/photos/add',
  qs: 
   { checkinId: [MY CHECKING ID],
     public: '1',
     oauth_token: [MY OAUTH TOKEN],
     v: [MY VERSION] },
  headers: 
   { 'postman-token': '8ce14473-b457-7f1a-eae2-ba384e99b983',
     'cache-control': 'no-cache',
     'content-type': 'multipart/form-data; boundary=----    WebKitFormBoundary7MA4YWxkTrZu0gW' },
  formData: 
   { file: 
      { value: 'fs.createReadStream("testimage.jpg")',
        options: { 
          filename: 'testimage.jpg', 
          contentType: null 
        } 
      } 
    } 
  };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});

The key part of this was fs.createReadStream(). The part I was missing before was to pass the image as a stream to the request.

Using this I was able to figure out the Axios request:

const axios = require("axios");
const querystring = require("qs");
const FormData = require("form-data");

const getImageStream = async function(url) {
  return await axios
    .get(url, {
      responseType: "stream"
    })
    .then(response => response.data);
};

let form = new FormData();
form.append("file", getImageStream([IMAGE URL]));

const requestURL = "https://api.foursquare.com/v2/photos/add";
const requestParams = {
  checkinId: [MY CHECKIN ID],
  public: 1,
  oauth_token: [MY OAUTH TOKEN],
  v: [MY VERSION]
};
const requestConfig = {
  headers: form.getHeaders()
};

try {
  const postPhoto = await axios.post(
    requestURL + "?" + querystring.stringify(requestParams),
    form,
    requestConfig
  );

  return postPhoto;
} catch (e) {
  console.error(e.response);
}

And voila, the request succeeds and the image is posted to the Foursquare checkin.

Upvotes: 1

Related Questions