GAS API連携の始め方|UrlFetchAppの使い方から認証・エラー対処まで解説
Google Apps Script(GAS)でスプレッドシートの処理を自動化できたものの、外部サービスとの連携方法がわからず行き詰まっている方は多いのではないでしょうか。Slack通知、ChatGPT呼び出し、freee会計との連携――これらは全てAPI連携で実現できます。
GASにはUrlFetchAppという組み込みクラスが用意されており、外部APIへのHTTPリクエストを数行のコードで実行できます。ただし、認証方式の違いやエラーハンドリングなど、実運用で詰まるポイントがいくつか存在します。
この記事では、UrlFetchAppの基本的な使い方から、OAuth 2.0認証の実装、よくあるエラーの対処法、そして実務で使える連携パターンまでを体系的に解説します。GAS初心者の方でも、この記事を読み終えたころにはAPI連携の全体像を理解し、自力で外部サービスとの連携を構築できるようになります。
この記事の目次
GAS API連携の全体像とUrlFetchAppの位置づけ
API連携とは、異なるソフトウェア同士がHTTP通信でデータをやり取りする仕組みです。たとえば「スプレッドシートに入力された受注データをSlackに自動通知する」場合、GASがSlackのAPIにHTTPリクエストを送信し、メッセージを投稿します。この「HTTPリクエストを送信する」部分を担うのがUrlFetchAppです。
UrlFetchAppとは何か
UrlFetchAppは、GASに標準搭載されたHTTPクライアントクラスです。外部のWebサーバーに対してGET、POST、PUT、DELETEなどのHTTPリクエストを送信し、レスポンスを受け取ることができます。追加ライブラリのインストールは不要で、GASのスクリプトエディタにそのままコードを書くだけで使えます。
GASはGoogleのサーバー上で実行されるため、ローカル環境のファイアウォールやプロキシの制約を受けません。社内のネットワーク制限でcurlコマンドが使えない環境でも、GASからのAPI呼び出しは問題なく動作します。この点は、非エンジニアがAPI連携を始める際の大きな利点です。
API連携で何ができるのか
GASのAPI連携で実現できることは多岐にわたります。代表的なユースケースを整理します。
| 連携先 | 用途 | 難易度 |
|---|---|---|
| Slack | スプレッドシート更新時にチャンネルへ通知 | 低 |
| ChatGPT(OpenAI) | スプレッドシートのテキストをAIで要約・分類 | 中 |
| freee会計 | 請求書データの取得・仕訳の自動登録 | 高 |
| Notion | スプレッドシートのデータをNotionデータベースに同期 | 中 |
| LINE Notify | フォーム回答をLINEグループに即時通知 | 低 |
難易度の違いは、主に認証方式の複雑さに起因します。Slack WebhookやLINE Notifyはトークンを1つ設定するだけで使えますが、freee会計のようにOAuth 2.0認証が必要なサービスは、トークンの取得・更新処理を自前で実装する必要があります。
UrlFetchAppの基本構文とGET/POSTリクエスト
GETリクエストの基本
外部APIからデータを取得するにはGETリクエストを使います。UrlFetchApp.fetch()メソッドにURLを渡すだけで実行できます。
function getWeather() {
var url = 'https://api.open-meteo.com/v1/forecast?latitude=33.59&longitude=130.40¤t_weather=true';
var response = UrlFetchApp.fetch(url);
var json = JSON.parse(response.getContentText());
Logger.log('気温: ' + json.current_weather.temperature + '℃');
}
fetch()の戻り値はHTTPResponseオブジェクトです。getContentText()でレスポンスボディを文字列として取得し、JSON.parse()でJavaScriptオブジェクトに変換します。APIのレスポンスがJSON形式であれば、この2ステップでデータを取り出せます。
POSTリクエストの基本
外部APIにデータを送信するにはPOSTリクエストを使います。fetch()の第2引数にオプションオブジェクトを渡し、メソッドやペイロードを指定します。
function postToSlack() {
var webhookUrl = 'https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX';
var payload = {
'text': 'スプレッドシートが更新されました'
};
var options = {
'method': 'post',
'contentType': 'application/json',
'payload': JSON.stringify(payload)
};
UrlFetchApp.fetch(webhookUrl, options);
}
ポイントは3つあります。methodに’post’を指定すること、contentTypeに’application/json’を指定すること、payloadにJSON.stringify()で文字列化したデータを渡すことです。多くのREST APIはJSON形式のリクエストボディを受け付けるため、この書き方を覚えておけば大半のPOSTリクエストに対応できます。
レスポンスの処理パターン
APIのレスポンスを適切に処理するには、ステータスコードの確認が欠かせません。以下は、ステータスコードを確認してからデータを処理するパターンです。
function fetchWithStatusCheck(url, options) {
var response = UrlFetchApp.fetch(url, Object.assign({}, options, {
'muteHttpExceptions': true
}));
var statusCode = response.getResponseCode();
var body = response.getContentText();
if (statusCode === 200) {
return JSON.parse(body);
} else {
Logger.log('エラー: ステータスコード ' + statusCode);
Logger.log('レスポンス: ' + body);
return null;
}
}
muteHttpExceptionsをtrueに設定すると、400番台・500番台のレスポンスでも例外が発生せず、ステータスコードを取得して分岐処理ができるようになります。本番運用のコードでは必ずこのオプションを入れてください。省略すると、APIエラー時にスクリプト全体が停止してしまいます。
API認証の種類と実装方法
外部APIを呼び出す際、ほとんどのサービスで認証が必要です。認証方式は主に3種類あり、連携先のサービスによって使い分けます。
| 認証方式 | 仕組み | 代表的なサービス |
|---|---|---|
| APIキー | 固定の文字列をヘッダーやURLに付与 | OpenAI、Google Maps、SendGrid |
| Webhook URL | 専用URLにPOSTするだけ(認証不要) | Slack Incoming Webhook、LINE Notify |
| OAuth 2.0 | アクセストークンを取得して使用(有効期限あり) | freee、Google Workspace、X(旧Twitter) |
APIキー認証の実装
最もシンプルな認証方式です。APIキーをHTTPヘッダーに含めてリクエストを送信します。OpenAIのAPIを例に見てみます。
function callChatGPT(prompt) {
var apiKey = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
var url = 'https://api.openai.com/v1/chat/completions';
var options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'Authorization': 'Bearer ' + apiKey
},
'payload': JSON.stringify({
'model': 'gpt-4o',
'messages': [{'role': 'user', 'content': prompt}]
}),
'muteHttpExceptions': true
};
var response = UrlFetchApp.fetch(url, options);
var json = JSON.parse(response.getContentText());
return json.choices[0].message.content;
}
APIキーはコード内にベタ書きせず、PropertiesService(スクリプトプロパティ)に保存してください。GASのスクリプトエディタで「プロジェクトの設定」を開き、スクリプトプロパティにキーを登録できます。コードにAPIキーを直書きすると、共有時に漏洩するリスクがあります。
OAuth 2.0認証の実装
OAuth 2.0は、ユーザーの代わりにAPIを操作する際に使われる認証方式です。freee会計やGoogleの各サービスが採用しています。GASでOAuth 2.0を実装するには、OAuth2 for Apps Scriptライブラリを使うのが現実的です。
手順は以下の通りです。
まず、GASのスクリプトエディタで「ライブラリ」を開き、スクリプトIDを入力して追加します。OAuth2 for Apps ScriptのスクリプトIDは「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」です。
次に、連携先サービスの開発者コンソールでOAuthクライアントIDとシークレットを取得します。リダイレクトURIには、GASのコールバックURL(https://script.google.com/macros/d/{スクリプトID}/usercallback)を設定します。
function getFreeeService() {
return OAuth2.createService('freee')
.setAuthorizationBaseUrl('https://accounts.secure.freee.co.jp/public_api/authorize')
.setTokenUrl('https://accounts.secure.freee.co.jp/public_api/token')
.setClientId(PropertiesService.getScriptProperties().getProperty('FREEE_CLIENT_ID'))
.setClientSecret(PropertiesService.getScriptProperties().getProperty('FREEE_CLIENT_SECRET'))
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('read write');
}
function authCallback(request) {
var service = getFreeeService();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('認証に成功しました。このタブを閉じてください。');
} else {
return HtmlService.createHtmlOutput('認証に失敗しました。');
}
}
function authorize() {
var service = getFreeeService();
if (!service.hasAccess()) {
var authUrl = service.getAuthorizationUrl();
Logger.log('以下のURLにアクセスして認証してください: ' + authUrl);
} else {
Logger.log('認証済みです');
}
}
OAuth2ライブラリは、アクセストークンの保存・期限切れ時の自動リフレッシュを内部で処理してくれます。一度認証が完了すれば、以降のAPIコールはservice.getAccessToken()でトークンを取得するだけです。
業務自動化でできること100選
経理・人事・営業・CS・総務・ITの6部門100事例を収録。各事例に月間削減時間と導入難易度を明記。今日から始められるチェックリスト付き。
実務で使えるAPI連携パターン3選
ここでは、中小企業の現場でよく使われるAPI連携パターンを3つ紹介します。コードはそのままコピーして使えるレベルで記載しています。
パターン1: スプレッドシートの更新をSlackに自動通知
スプレッドシートに新しい行が追加されたタイミングで、Slackの指定チャンネルにメッセージを送信するパターンです。Slack Incoming Webhookを使うため、認証の設定は不要です。
function notifySlackOnEdit(e) {
var sheet = e.source.getActiveSheet();
if (sheet.getName() !== '受注管理') return;
var row = e.range.getRow();
var company = sheet.getRange(row, 1).getValue();
var amount = sheet.getRange(row, 3).getValue();
var webhookUrl = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_URL');
var payload = {
'text': '受注登録がありました\n企業名: ' + company + '\n金額: ' + amount.toLocaleString() + '円'
};
UrlFetchApp.fetch(webhookUrl, {
'method': 'post',
'contentType': 'application/json',
'payload': JSON.stringify(payload)
});
}
この関数をGASのトリガー設定で「編集時」に紐づければ、スプレッドシートに行が追加されるたびにSlackに通知が飛びます。Slack側のWebhook URLは、SlackアプリのIncoming Webhook設定画面で取得できます。
パターン2: ChatGPT APIで問い合わせ内容を自動分類
Googleフォームから届く問い合わせ内容を、ChatGPT APIで「見積依頼」「技術質問」「クレーム」「その他」に自動分類し、スプレッドシートに結果を書き込むパターンです。
function classifyInquiry() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('問い合わせ');
var lastRow = sheet.getLastRow();
for (var i = 2; i <= lastRow; i++) {
var classified = sheet.getRange(i, 4).getValue();
if (classified !== '') continue;
var content = sheet.getRange(i, 3).getValue();
var category = callChatGPTForClassification(content);
sheet.getRange(i, 4).setValue(category);
Utilities.sleep(1000);
}
}
function callChatGPTForClassification(text) {
var apiKey = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
var options = {
'method': 'post',
'contentType': 'application/json',
'headers': {'Authorization': 'Bearer ' + apiKey},
'payload': JSON.stringify({
'model': 'gpt-4o-mini',
'messages': [
{'role': 'system', 'content': '以下のテキストを「見積依頼」「技術質問」「クレーム」「その他」のいずれかに分類してください。カテゴリ名のみ回答してください。'},
{'role': 'user', 'content': text}
],
'temperature': 0
}),
'muteHttpExceptions': true
};
var response = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', options);
var json = JSON.parse(response.getContentText());
return json.choices[0].message.content.trim();
}
temperatureを0に設定することで、分類結果のブレを最小限に抑えています。gpt-4o-miniを使えば1リクエストあたりの費用は極めて低く、月に数百件の問い合わせを処理しても数十円程度で済みます。
パターン3: 外部APIからデータを取得してスプレッドシートに一覧化
外部サービスのAPIからデータを取得し、スプレッドシートに自動で一覧化するパターンです。ここではオープンな天気APIを使った例を示します。
function fetchWeatherData() {
var cities = [
{name: '東京', lat: 35.68, lon: 139.69},
{name: '大阪', lat: 34.69, lon: 135.50},
{name: '福岡', lat: 33.59, lon: 130.40}
];
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('天気データ');
sheet.getRange(1, 1, 1, 4).setValues([['都市', '気温(℃)', '風速(m/s)', '取得日時']]);
for (var i = 0; i < cities.length; i++) {
var url = 'https://api.open-meteo.com/v1/forecast?latitude=' + cities[i].lat
+ '&longitude=' + cities[i].lon + '¤t_weather=true';
var response = UrlFetchApp.fetch(url, {'muteHttpExceptions': true});
if (response.getResponseCode() === 200) {
var data = JSON.parse(response.getContentText());
var weather = data.current_weather;
sheet.getRange(i + 2, 1, 1, 4).setValues([
[cities[i].name, weather.temperature, weather.windspeed, new Date()]
]);
}
Utilities.sleep(500);
}
}
ループ内でUtilities.sleep()を挟むのは、APIのレート制限(一定時間内のリクエスト回数制限)に引っかからないようにするためです。多くのAPIは1秒あたりのリクエスト数に上限を設けています。この習慣を身につけておくと、本番運用でのエラーを防げます。
エラー対処とデバッグのコツ
API連携で最も時間を取られるのがエラー対処です。UrlFetchAppで発生するエラーは大きく3種類に分かれます。それぞれの原因と対処法を整理します。
HTTPステータスコード別の対処法
| ステータスコード | 意味 | 主な原因 | 対処法 |
|---|---|---|---|
| 400 | Bad Request | リクエストボディの形式が不正 | JSONの構造・必須パラメータを確認 |
| 401 | Unauthorized | 認証情報が無効 | APIキー・トークンの有効期限を確認 |
| 403 | Forbidden | 権限不足 | APIキーのスコープ・権限設定を確認 |
| 404 | Not Found | エンドポイントURLが間違っている | APIドキュメントでURLを再確認 |
| 429 | Too Many Requests | レート制限に到達 | リクエスト間隔を広げる(sleepを入れる) |
| 500 | Internal Server Error | API提供側のサーバー障害 | 時間を置いてリトライ |
リトライ処理の実装
一時的なエラー(429、500、502、503)に対しては、自動リトライを組み込むことで安定性が大幅に向上します。以下は指数バックオフ(リトライ間隔を徐々に広げる)を実装した例です。
function fetchWithRetry(url, options, maxRetries) {
maxRetries = maxRetries || 3;
var retryableStatuses = [429, 500, 502, 503];
for (var attempt = 0; attempt <= maxRetries; attempt++) {
var response = UrlFetchApp.fetch(url, Object.assign({}, options, {
'muteHttpExceptions': true
}));
var statusCode = response.getResponseCode();
if (statusCode === 200 || statusCode === 201) {
return JSON.parse(response.getContentText());
}
if (retryableStatuses.indexOf(statusCode) === -1) {
throw new Error('リトライ不可のエラー: ' + statusCode + ' ' + response.getContentText());
}
if (attempt < maxRetries) {
var waitTime = Math.pow(2, attempt) * 1000;
Logger.log('リトライ ' + (attempt + 1) + '回目: ' + waitTime + 'ms待機');
Utilities.sleep(waitTime);
}
}
throw new Error('最大リトライ回数に到達: ' + url);
}
指数バックオフでは、1回目のリトライは1秒後、2回目は2秒後、3回目は4秒後と待機時間が倍増していきます。これにより、API側の一時的な過負荷が解消されるまで適切に待機できます。
デバッグで使えるテクニック
APIのレスポンスが期待通りでない場合、以下の手順でデバッグを進めます。
まず、レスポンスの全文をLogger.logで出力します。APIのエラーレスポンスには原因を示すメッセージが含まれていることが多く、ログを見るだけで問題が特定できるケースが大半です。
次に、同じリクエストをcurlコマンドで再現してみます。GASの問題なのかAPI側の問題なのかを切り分けるためです。UrlFetchAppのオプションをcurlに変換するには、以下のように対応させます。
# GASのfetch(url, {method:'post', contentType:'application/json', headers:{Authorization:'Bearer xxx'}, payload:'{"key":"value"}'})
# ↓ curlに変換
curl -X POST https://api.example.com/endpoint \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xxx" \
-d '{"key":"value"}'
curlで成功するのにGASで失敗する場合、GAS側のヘッダー設定やペイロードの形式に問題がある可能性が高いです。特に、payloadをJSON.stringify()し忘れているケースが頻出します。
API連携の運用で押さえるべき制限事項
GASのAPI連携を本番運用する際には、GAS側とAPI側の両方に存在する制限事項を把握しておく必要があります。制限を超えるとスクリプトが途中で停止し、データの不整合が発生するリスクがあります。
GAS側の主な制限
Google Apps Scriptには実行時間やリクエスト回数に関する制限があります。Google公式の制限一覧ページに最新の数値が記載されていますが、主要なものを整理します。
| 制限項目 | 無料アカウント | Google Workspace |
|---|---|---|
| スクリプト実行時間 | 6分/実行 | 6分/実行 |
| UrlFetchAppのリクエスト数 | 20,000回/日 | 100,000回/日 |
| レスポンスサイズ | 50MB/回 | 50MB/回 |
| トリガーの合計実行時間 | 90分/日 | 6時間/日 |
6分の実行時間制限は特に注意が必要です。大量のAPIリクエストをループで回す場合、処理が6分を超えるとスクリプトが強制終了します。対策としては、1回の実行で処理する件数を制限し、残りは次回のトリガー実行で処理する「分割実行」方式を採用します。処理済みの行番号をスクリプトプロパティに保存しておけば、次回はその続きから処理を再開できます。
APIキー管理のベストプラクティス
運用上、最も注意すべきはAPIキーの管理です。以下のルールを守ってください。
コード内にAPIキーをベタ書きしないでください。必ずPropertiesService.getScriptProperties()に保存し、getProperty()で取得します。スクリプトプロパティは暗号化されて保存されるため、コードを共有してもキーが漏洩しません。
APIキーにはスコープ(権限範囲)を最小限に設定してください。たとえばfreee APIで「仕訳の参照」だけが必要なら、書き込み権限は付与しません。万が一キーが漏洩した場合の被害を最小化できます。
定期的にAPIキーをローテーション(再発行・旧キーの無効化)してください。多くのAPIサービスは管理コンソールからワンクリックでキーを再発行できます。3ヶ月に1回程度の頻度でローテーションするのが理想的です。
実行ログの記録
API連携が増えてくると、どの連携がいつ実行され、成功したのか失敗したのかを追跡する仕組みが必要になります。専用のログシートを用意し、実行結果を書き込む関数を共通化しておくと、トラブル発生時の原因特定が格段に速くなります。
function logApiCall(apiName, status, message) {
var logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('APIログ');
if (!logSheet) {
logSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('APIログ');
logSheet.getRange(1, 1, 1, 4).setValues([['日時', 'API名', 'ステータス', 'メッセージ']]);
}
logSheet.appendRow([new Date(), apiName, status, message]);
}
この関数をfetchWithRetry()と組み合わせれば、全てのAPI呼び出しの履歴がスプレッドシートに自動記録されます。エラーが発生した際にログを確認すれば、いつ・どのAPIで・何が起きたのかを即座に把握できます。
Q.UrlFetchAppで接続できるAPIに制限はありますか
A.HTTPまたはHTTPSでアクセスできるAPIであれば、基本的に接続可能です。ただし、IPアドレスでアクセス制限をかけているAPIの場合、GASの実行サーバーのIPがホワイトリストに含まれていないと接続できません。GASの実行IPはGoogleのクラウドIPレンジから動的に割り当てられるため、IP制限のあるAPIとの連携は難しい場合があります。
Q.APIキーをPropertiesService以外で管理する方法はありますか
A.Google Cloud Secret Managerを使う方法があります。GASからSecret Manager APIを呼び出してシークレットを取得します。ただし、設定がやや複雑になるため、通常のGASプロジェクトであればPropertiesServiceで十分です。複数のスクリプトで同じAPIキーを共有したい場合にSecret Managerが有効です。
Q.UrlFetchAppでファイルをアップロードすることはできますか
A.可能です。multipart/form-data形式でリクエストを送信します。payloadにBlobオブジェクトを含めることで、画像やPDFなどのバイナリファイルをAPIに送信できます。GoogleドライブのファイルをgetBlob()で取得してそのまま送信するパターンがよく使われます。
Q.GASのAPI連携とZapierやMakeなどのノーコードツールはどう使い分けますか
A.定型的な「AのイベントでBに通知」程度の連携であれば、ZapierやMakeのほうが設定が簡単です。一方、条件分岐が複雑な処理、スプレッドシートのデータを加工してから送信する処理、複数のAPIを組み合わせる処理はGASのほうが柔軟に対応できます。また、GASはGoogle Workspaceユーザーなら追加コストなしで使える点も大きなメリットです。
まとめ
この記事のポイント
- UrlFetchAppはGAS標準搭載のHTTPクライアントで、追加ライブラリなしで外部APIを呼び出せる
- GETリクエストはfetch(url)、POSTリクエストはfetch(url, options)でmethod/contentType/payloadを指定する
- API認証は「APIキー」「Webhook URL」「OAuth 2.0」の3種類があり、連携先に応じて使い分ける
- muteHttpExceptionsをtrueに設定し、ステータスコードで分岐処理を行うことでエラーに強いコードになる
- リトライ処理(指数バックオフ)を組み込むと一時的なエラーへの耐性が大幅に向上する
- GASの実行時間制限(6分)とURLFetchの日次リクエスト上限を考慮した設計が本番運用には必須
- APIキーはPropertiesServiceに保存し、コード内へのベタ書きは避ける
関連記事:GASの実践活用
2026年4月