title: Proper format of a Google Drive API multipart/mixed batch request using Javascript
body: <p>I am using Javascript (in the Google Apps Script Editor) to make HTTP requests to the Google Drive API. </p>
  
  <p>I've successfully made individual calls to the API, so I know my Oauth 2 works, but now I'm working on making a batch request.</p>
  
  <p>I will loop through each file, and make 2 API calls per file. </p>
  
  <p>My request: </p>
  
  <pre><code>var newOwnerEmail = 'archive@test.com';
  var newEditorEmail = 'admin@test.com';
  var fileIds = ['a', 'b', 'c'];
  
  const boundary = 'testboundaryname';
  const delimiter = "\r\n--" + boundary + "\r\n";
  const close_delim = "\r\n--" + boundary + "--";
  
  var multipartRequestBody = '';
  
  for (var i = 0; i &lt; fileIds.length; i++) {
  
    var fileId = fileIds[i];
  
    var resource1 = { role: "owner", type: "user", value: newOwnerEmail };
  
    var requestBody = {};
    requestBody.path = '/drive/v2/files/' + fileId + '/permissions';
    requestBody.method = 'POST';
    requestBody.contentType = 'application/json';
    requestBody.payload = JSON.stringify(resource1);
    requestBody.muteHttpExceptions = true;
  
    multipartRequestBody += delimiter;
    multipartRequestBody += 'Content-Type: application/json\r\n';
    multipartRequestBody += JSON.stringify(requestBody);
  
    var resource2 = { role: "writer", type: "user", value: newEditorEmail };
  
    requestBody.payload = JSON.stringify(resource2);
  
    multipartRequestBody += delimiter;
    multipartRequestBody += 'Content-Type: application/json\r\n';
    multipartRequestBody += JSON.stringify(requestBody);
  
  }
  
  multipartRequestBody += close_delim;
  
  var batchUrl = 'https://www.googleapis.com/batch';
  
  var batchRequestBody = {};
  batchRequestBody.headers = { Authorization: 'Bearer ' + service.getAccessToken() };
  batchRequestBody.method = 'POST';
  batchRequestBody.contentType = 'multipart/mixed; boundary="' + boundary + '"';
  batchRequestBody.body = multipartRequestBody;
  batchRequestBody.muteHttpExceptions = true;
  
  try {
  
    var batchResponse = UrlFetchApp.fetch(batchUrl, batchRequestBody);
  
    Logger.log(batchResponse);
  </code></pre>
  
  <p><code>multipartRequestBody</code> ends up being a string that looks like this: </p>
  
  <pre><code> --testboundary
   Content-Type: application/json
   {"method":"POST","contentType":"application/json","payload":"{\"role\":\"owner\",\"type\":\"user\",\"value\":\"archive@test.com\"}","muteHttpExceptions":false}
   --testboundary
   Content-Type: application/json
   {"method":"POST","contentType":"application/json","payload":"{\"role\":\"writer\",\"type\":\"user\",\"value\":\"admin@test.com\"}","muteHttpExceptions":false}
   --testboundary
   Content-Type: application/json
   {"method":"POST","contentType":"application/json","payload":"{\"role\":\"owner\",\"type\":\"user\",\"value\":\"archive@test.com\"}","muteHttpExceptions":false}
   --testboundary
   Content-Type: application/json
   {"method":"POST","contentType":"application/json","payload":"{\"role\":\"writer\",\"type\":\"user\",\"value\":\"admin@test.com\"}","muteHttpExceptions":false}
   --testboundary
   Content-Type: application/json
   {"method":"POST","contentType":"application/json","payload":"{\"role\":\"owner\",\"type\":\"user\",\"value\":\"archive@test.com\"}","muteHttpExceptions":false}
   --testboundary
   Content-Type: application/json
   {"method":"POST","contentType":"application/json","payload":"{\"role\":\"writer\",\"type\":\"user\",\"value\":\"admin@test.com\"}","muteHttpExceptions":false}
   --testboundary--
  </code></pre>
  
  <p>I ran <code>UrlFetchApp.getRequest()</code> to see what was being sent to the server, and it's this: </p>
  
  <pre><code>{
    "headers": {
      "Authorization": "Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "X-Forwarded-For": “(our external IP address)“
    },
    "method": "post",
    "payload": "\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"owner\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"archive@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"writer\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"admin@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"owner\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"archive@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"writer\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"admin@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"owner\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"archive@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"writer\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"admin@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"owner\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"archive@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"writer\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"admin@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"owner\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"archive@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary\r\nContent-Type: application/json\r\n{\"method\":\"POST\",\"contentType\":\"application/json\",\"payload\":\"{\\\"role\\\":\\\"writer\\\",\\\"type\\\":\\\"user\\\",\\\"value\\\":\\\"admin@test.com\\\"}\",\"muteHttpExceptions\":false}\r\n--testboundary--",
    "followRedirects": true,
    "validateHttpsCertificates": true,
    "useIntranet": false,
    "contentType": "multipart/mixed; boundary=\"testboundary\"",
    "url": "https://www.googleapis.com/batch"
  }
  </code></pre>
  
  <p>I get this response: </p>
  
  <pre><code>Unsupported number format: --
  </code></pre>
  
  <p>There's definitely something wrong with the content of the request, but it's difficult to determine what it is, given that there's such little documentation for creating batch requests using Javascript.</p>
  
  <p>The only documentation I could find is <a href="https://developers.google.com/drive/web/batch" rel="nofollow">this</a>, but the example is language-independent/hypothetical. </p>
  
  <p>Then I found <a href="https://developers.google.com/drive/web/quickstart/quickstart-js" rel="nofollow">this example</a>, which seems to use a Google API client library, which I can't use in Apps Script. </p>