I am experiencing an issue where API calls from Google Apps Script fail with a 404 error, but the exact same request works perfectly from the API Explorer.
- **Fails From:** Google Apps Script using `UrlFetchApp`. It consistently returns an HTTP 404 error with a generic HTML "Not Found" page.
- **Succeeds From:** The API Explorer on the official documentation page, using the same Google Account for OAuth and the same Developer Token. This proves that my credentials, developer token, and account permissions are all correct.
- Confirmed the Google Ads API is enabled in my GCP project.
- Confirmed the OAuth Consent Screen is in "Testing" mode and my email is added as a test user.
- Tried changing the `User-Agent` header in the Apps Script request.
Could you please investigate why requests from the Apps Script environment are being rejected? There seems to be an issue specific to requests originating from `UrlFetchApp`.
**1. Logs from the FAILED request (from Google Apps Script)**
Please paste the entire execution log from the last Apps Script run here.
**2. The Apps Script Code**
This is the full code used for the test.
// ----------------------------------------------------
// ここにあなたの情報を入力してください
// ----------------------------------------------------
const CLIENT_ID = 'ご自身のクライアントID'; // ★ GCPで取得したあなたのクライアントIDをここに貼り付け
const CLIENT_SECRET = 'ご自身のクライアントシークレット'; // ★ GCPで取得したあなたのクライアントシークレットをここに貼り付け
const CUSTOMER_ID = 'ご自身のGoogle広告ID'; // ★ Google広告のクライアントID (ハイフンなし) をここに貼り付け
const DEVELOPER_TOKEN = 'ご自身の開発者トークン'; // ★ Google広告の「APIセンター」から取得し、ここに貼り付け
// Google Ads APIのバージョン
const API_VERSION = 'v16';
// ----------------------------------------------------
function authenticateGoogleAds() {
const service = getGoogleAdsService();
if (!service.hasAccess()) {
Logger.log('認証が必要です: ' + service.getAuthorizationUrl());
return;
}
Logger.log('すでに認証済みです。');
try {
const accessToken = service.getAccessToken();
Logger.log('\nアクセス可能顧客ID一覧を取得中...');
sendGoogleAdsApiRequest(accessToken, null, `/${API_VERSION}/customers:listAccessibleCustomers`, {});
} catch (e) {
Logger.log('スクリプトの実行中にエラーが発生しました。詳細はログを確認してください。');
}
}
function sendGoogleAdsApiRequest(accessToken, customerId, apiPath, payload) {
const headers = {
'Authorization': `Bearer ${accessToken}`,
'developer-token': DEVELOPER_TOKEN,
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' };
if (customerId) {
headers['login-customer-id'] = customerId;
}
const options = {
'method': 'post',
'contentType': 'application/json',
'headers': headers,
'payload': JSON.stringify(payload),
'muteHttpExceptions': true
};
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
const responseHeaders = response.getHeaders();
// ★★★ Googleへの問い合わせに必須のリクエストIDを取得 ★★★
const requestId = responseHeaders['request-id'] || responseHeaders['Request-Id'] || 'N/A';
Logger.log(`--- API Response (Path: ${apiPath}) ---`);
Logger.log(`Status Code: ${responseCode}`);
Logger.log(`Request ID: ${requestId}`); // このIDが最も重要です
if (responseCode >= 200 && responseCode < 300) {
Logger.log("Response Body (Success):");
Logger.log(JSON.stringify(JSON.parse(responseBody), null, 2));
return JSON.parse(responseBody);
} else {
Logger.log("Response Body (Error):");
Logger.log(responseBody);
throw new Error(`Google Ads API Request Failed. Code: ${responseCode}, Request ID: ${requestId}`);
}
}
// OAuth2関連の関数 (変更なし)
function getGoogleAdsService() {
}
function authCallback(request) {
const service = getGoogleAdsService();
if (service.handleCallback(request)) { return HtmlService.createHtmlOutput('認証に成功しました。'); } else { return HtmlService.createHtmlOutput('認証に失敗しました。'); }
}
Thank you for your help.