[SalesForce] Upload an Attachment via the Rest API

I'm having an issue uploading attachments via the REST API. When trying to upload attachment data (i.e. data about the attachment and the associated base64 binary data) I'm getting a 400 result (Bad Request) with a message like so:

Illegal Request
You have sent us an Illegal URL or an improperly formatted request.

My posted request body looks like this:

--boundary_string_1414605653846
Content-Disposition: form-data; name="entity_attachment";
Content-type: application/json

{"ParentId":"a01J000000gmQlRIAU","Name":"Troubleshooting Request","OwnerId":"005J000000253nKIAQ"}

--boundary_string_1414605653846
Content-Type: application/octet-stream
Content-Disposition: form-data; name="Body"; filename="test.txt"

dGVzdA==

--boundary_string_1414605653846--

Relevant header information:

Request Method:POST
Content-Type:multipart/form-data; "boundary_string_1414605653846"

I used the Salesforce Insert/Update Blob Tutorial to get to the point where I am. Note that their example only interacts with the Document type (not Attachment). However, I tried copying their Document Insert example line-by-line and I still received the 400 Bad Request error.

One other piece of information is that I am not uploading an actual file, but rather a large base64 encoded string.

Best Answer

You can do this using forcetk if you are planning to do this in javascript

here is sample code -

<apex:page docType="html-5.0" title="File Uploader">

Select a file to upload as a new Chatter File.

var client = new forcetk.Client();

client.setSessionToken('{!$Api.Session_ID}');

function upload() {
    var file = $("#file")[0].files[0];
    client.createBlob('ContentVersion', {
        Origin: 'H', // 'H' for Chatter File, 'C' for Content Document
        PathOnClient: file.name
    }, file.name, 'VersionData', file, function(response){
        console.log(response);
        $("#message").html("Chatter File created: <a target=\"_blank\" href=\"/" + response.id + "\">Take a look!</a>");
    }, function(request, status, response){
        $("#message").html("Error: " + status);
    });
}

here is internal forcetk implementation for sf blob [link - https://github.com/developerforce/Force.com-JavaScript-REST-Toolkit/blob/master/forcetk.js] -

    /* Low level function to create/update records with blob data
 * @param path resource path relative to /services/data
 * @param fields an object containing initial field names and values for 
 *               the record, e.g. {ContentDocumentId: "069D00000000so2", 
 *               PathOnClient: "Q1 Sales Brochure.pdf"}
 * @param filename filename for blob data; e.g. "Q1 Sales Brochure.pdf"
 * @param payloadField 'VersionData' for ContentVersion, 'Body' for Document
 * @param payload Blob, File, ArrayBuffer (Typed Array), or String payload
 * @param callback function to which response will be passed
 * @param [error=null] function to which response will be passed in case of error
 * @param retry true if we've already tried refresh token flow once
 */
forcetk.Client.prototype.blob = function(path, fields, filename, payloadField, payload, callback, error, retry) { 
    var that = this;
    var url = (this.visualforce ? '' : this.instanceUrl) + '/services/data' + path;
    var boundary = randomString();

    var blob = new Blob([
        "--boundary_" + boundary + '\n' 
        + "Content-Disposition: form-data; name=\"entity_content\";" + "\n" 
        + "Content-Type: application/json" + "\n\n" 
        + JSON.stringify(fields) 
        + "\n\n" 
        + "--boundary_" + boundary + "\n" 
        + "Content-Type: application/octet-stream" + "\n" 
        + "Content-Disposition: form-data; name=\"" + payloadField 
          + "\"; filename=\"" + filename + "\"\n\n",
        payload,
        "\n\n" 
        + "--boundary_" + boundary + "--"
    ], {type : 'multipart/form-data; boundary=\"boundary_' + boundary + '\"'});

    var request = new XMLHttpRequest();
    request.open("POST", (this.proxyUrl !== null && ! this.visualforce) ? this.proxyUrl: url, this.asyncAjax);

    request.setRequestHeader('Accept', 'application/json');
    request.setRequestHeader(this.authzHeader, "Bearer " + this.sessionId);
    request.setRequestHeader('X-User-Agent', 'salesforce-toolkit-rest-javascript/' + this.apiVersion);
    if (this.proxyUrl !== null && ! this.visualforce) {
        request.setRequestHeader('SalesforceProxy-Endpoint', url);
    }

    if (this.asyncAjax) {
        request.onreadystatechange = function() {
            // continue if the process is completed
            if (request.readyState == 4) {
                // continue only if HTTP status is good
                if (request.status >= 200 && request.status < 300) {
                    // retrieve the response
                    callback(request.response ? JSON.parse(request.response) : null);
                } else if(request.status == 401 && !retry) {
                    that.refreshAccessToken(function(oauthResponse) {
                        that.setSessionToken(oauthResponse.access_token, null,oauthResponse.instance_url);
                        that.blob(path, fields, fileName, file, callback, error, true);
                    },
                    error);
                } else {
                    // return status message
                    error(request, request.statusText, request.response);
                }
            }            
        }
    }

    request.send(blob);

    return this.asyncAjax ? null : JSON.parse(request.response);
}
Related Topic