You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google服务账号代授权批量复制Drive文件遇404错误求助

Service Account Domain-Wide Delegation: 404 "File not found" When Batch Copying Drive Files

I'm trying to authenticate as a domain user using a service account, and I've already set up domain-wide delegation in the G Suite Admin Console. I can successfully retrieve an access token with the Google Apps Script code below, but when I send batch requests to copy Drive files, I get a code: 404, message: 'File not found:' error. Did I miss any steps in the service account creation or authentication flow?

My Code:

Access Token Retrieval:

var CREDENTIALS = { 
  private_key: "-----BEGIN PRIVATE KEY----- XXXXXXX \n-----END PRIVATE KEY-----\n", 
  client_email: "XXXXXX@fXXXXXX.iam.gserviceaccount.com", 
  client_id: "1XXXXXXXXXXXXXXXX", 
  user_email: "XXXXX@XXXX.XXX.XXX", 
  scopes: [
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/script.external_request"
  ] 
}; 

function oAuthToken(){ 
  var url = "https://www.googleapis.com/oauth2/v3/token"; 
  var header = { alg: "RS256", typ: "JWT" }; 
  var now = Math.floor(Date.now() / 1000); 
  var claim = { 
    iss: CREDENTIALS.client_id, 
    sub: CREDENTIALS.user_email, 
    scope: CREDENTIALS.scopes.join(" "), 
    aud: url, 
    exp: (now + 3600).toString(), 
    iat: now.toString() 
  }; 

  var signature = Utilities.base64Encode(JSON.stringify(header)) + "." + Utilities.base64Encode(JSON.stringify(claim)); 
  var jwt = signature + "." + Utilities.base64Encode(Utilities.computeRsaSha256Signature(signature, CREDENTIALS.private_key)); 

  var params = { 
    method: "post", 
    payload: { 
      assertion: jwt, 
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer" 
    } 
  }; 

  var res = UrlFetchApp.fetch(url, params).getContentText(); 
  return JSON.parse(res); 
} 

Batch Processing (Abbreviated):

var request={ batchPath: requests:[] }; 
var backoff = 0;

function batch(request) { 
  var oAuth = oAuthToken().access_token; 
  var url = 'https://www.googleapis.com/'+request.batchPath; 
  var body = request.requests; 

  if(body.length < 1){ return []; } 

  var boundary = 'xxxxxxxxxx'; 
  var contentId = 0; 
  var data = '--' + boundary + '\r\n'; 

  for (var i in body) { 
    if(typeof body[i]=='object'){ 
      data += 'Content-Type: application/http\r\n'; 
      data += 'Content-ID: ' + ++contentId + '\r\n\r\n'; 
      data += body[i].method + ' ' + body[i].endpoint + '\r\n'; 
      data += body[i].requestBody ? 'Content-Type: application/json; charset=utf-8\r\n\r\n' : '\r\n'; 
      data += body[i].requestBody ? JSON.stringify(body[i].requestBody) + '\r\n' : ''; 
      data += "--" + boundary + '\r\n'; 
    } 
  } 

  var parseBatchRes = function(res) { 
    var splittedRes = res.split('--batch'); 
    return splittedRes.slice(1, splittedRes.length - 1).map(function(e) { 
      return { 
        contentId: Number(e.match(/Content-ID: response-(\d+)/)[1]), 
        status: Number(e.match(/HTTP\/\d+.\d+ (\d+)/)[1]), 
        object: JSON.parse(e.match(/{[\S\s]+}/)[0]) 
      }; 
    }); 
  }; 

  var payload = Utilities.newBlob(data).getBytes(); 
  var head = {Authorization: 'Bearer ' + oAuth}; 
  var options = { 
    method: 'POST', 
    contentType: 'multipart/mixed; boundary=' + boundary, 
    payload: payload, 
    headers: head, 
    muteHttpExceptions: false 
  }; 

  var complete=false; 
  var finalResponse=[]; 
  for (var n=0; n<=backoff; n++) { 
    if(complete){ break; } 
    var complete = true; 
    console.log('backoff',n); 
    var response = UrlFetchApp.fetch(url, options).getContentText(); 
    for(var j=0;j<response.length;j++){ 
      if(response[r].status!=200){ 
        var complete = false; 
      } 
    } 
  } 
}

Let's break down the potential issues and fixes here:

1. JWT Encoding is Not URL-Safe

The biggest hidden issue here is your JWT encoding method. Utilities.base64Encode() generates standard Base64, but JWT requires URL-safe Base64 (replace + with -, / with _, and strip trailing = characters). Even if you get an access token, a non-url-safe JWT might not properly delegate the user's Drive permissions, leading to permission-related 404s.

Add this helper function for URL-safe encoding:

function base64UrlEncode(str) {
  return Utilities.base64Encode(str)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

Then update your oAuthToken() function to use this instead of the standard encoding:

var signature = base64UrlEncode(JSON.stringify(header)) + "." + base64UrlEncode(JSON.stringify(claim));
var signatureBytes = Utilities.computeRsaSha256Signature(signature, CREDENTIALS.private_key);
var jwt = signature + "." + base64UrlEncode(signatureBytes);

2. Verify Batch Request Endpoints & File Permissions

A 404 "File not found" almost always points to an invalid file ID or missing access:

  • Double-check that the file IDs in your body[i].endpoint are correct (no typos) and that the delegated user (CREDENTIALS.user_email) has view or edit access to those files.
  • Ensure your endpoint uses the correct Drive API version: For v3, the copy endpoint is https://www.googleapis.com/drive/v3/files/{FILE_ID}/copy (POST method).
  • Confirm your batchPath is set to batch/drive/v3 for Drive v3 batch requests.

3. Double-Check Domain-Wide Delegation Setup

Even if you think it's configured correctly, verify these steps in the G Suite Admin Console:

  • Go to Security > API Controls > Domain-wide delegation.
  • Confirm your service account's client ID is listed, and the scopes include https://www.googleapis.com/auth/drive (not a restricted scope like drive.readonly).
  • Ensure the delegated user (CREDENTIALS.user_email) is a valid domain user with active Drive access.

4. Fix Batch Response Parsing Typo

Your current batch loop has a typo (response[r].status instead of response[j].status), which means you're not properly detecting failed requests. Fix this to debug which file is causing the 404:

var parsedResponses = parseBatchRes(response);
for(var j=0;j<parsedResponses.length;j++){
  if(parsedResponses[j].status !== 200){
    complete = false;
    // Log the specific error to pinpoint the problematic file
    console.log('Failed request details:', parsedResponses[j].object);
  }
}

5. Shared Drive Edge Case

If the files are in a shared drive, ensure the delegated user has access to the shared drive itself. In rare cases, you might need to add the service account as a member of the shared drive with edit/owner permissions, but this shouldn't be necessary if the delegated user already has access.

Start with the JWT encoding fix—it's the most common hidden issue with service account delegation in Apps Script. Once that's sorted, use the updated error logging to pinpoint exactly which file is failing and verify its permissions.

内容的提问来源于stack exchange,提问作者Clay Smith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 21:27:45