こんにちは、こんです🦊
今日は、Logiless APIをGAS(Google Apps Script)で扱うためのOAuth2認証環境を整えました!
「配送データを自動取得したい」「返品ステータスを定期チェックしたい」など、Logiless APIを活用した自動化は非常に便利ですが、最初のハードルがOAuth2認証の構築。
今回はそれをゼロからGASで実装し、誰でも社内で使える形に仕上げました。
実験のきっかけ:「認証って面倒そう…」
Logiless APIを使いたいけど「OAuthって難しそう」
トークンの有効期限切れでエラーになる
複数メンバーに使ってもらいたいが、再認証が面倒
「だったら一度しっかり整えて、誰でも再利用できるようにしよう」と思い、取り組んでみました。
作ってみたもの(システムの概要)
今回は以下のような認証基盤を構築しました:
✅ 認証用URLの自動生成&UI表示
✅ 初回認証時のトークン保存(access_token / refresh_token / 有効期限)
✅ トリガーやUIからの自動リフレッシュ
✅ リフレッシュトークンの再取得にも対応
✅ 有効期限の残り時間をユーザーに表示
✅ エラー発生時のSlack通知やログ出力(※発展編)
すべてGAS(Google Apps Script)だけで完結しています。
実感したメリット
使ってみて感じたことを3つにまとめてみました。
1. トークン管理が“見える化”された
「あと何時間で切れるか?」がUIで確認できるようになり、エラー前の更新判断が可能になりました。
2. 一度認証すれば“自動で更新”
初回だけブラウザ認証をすれば、その後はバックグラウンドで自動リフレッシュ。定期実行トリガーとも親和性◎。
3. エラー時の対応が楽になった
リフレッシュ失敗時やJSONパース失敗などもログに出力し、UIで通知するようにしているので、運用時の安心感が段違いです。
実際の画面イメージ(構成)
【認証メニュー】:スプレッドシート上のメニューから「認証開始」「状況確認」「手動更新」などを選択できるように
【認証URLダイアログ】:初回認証時にログインURLを表示して、認証完了後にトークン保存
【認証チェック表示】:トークンの有効期限や残り時間をダイアログで可視化
🔧 Logiless API × GAS 完全認証スクリプト(全文)
🔐 注意:client_id と client_secret は Logiless開発者ページ で取得したものを自身でセットしてください。
// === 認証用URLを生成し、ユーザーに表示 ===
function authorize() {
const service = getOAuthService();
const authorizationUrl = service.getAuthorizationUrl();
const message = service.hasAccess()
? "LOGILESS APIの認証は既に完了しています。"
: `<p>LOGILESS APIの認証が必要です。<br>以下のURLを開いて認証してください:</p>
<a href="${authorizationUrl}" target="_blank">${authorizationUrl}</a>`;
showDialog(message, "認証状況");
}
// === クライアントID/シークレットを保存 ===
function setClientCredentials() {
const props = PropertiesService.getScriptProperties();
props.setProperty('client_id', 'YOUR_CLIENT_ID'); // ← 自分のIDに差し替えてください
props.setProperty('client_secret', 'YOUR_CLIENT_SECRET'); // ← 自分のシークレットに差し替えてください
}
// === OAuth2認証情報を取得 ===
function getOAuthCredentials() {
const props = PropertiesService.getScriptProperties();
return {
clientId: props.getProperty('client_id'),
clientSecret: props.getProperty('client_secret'),
};
}
// === OAuth2サービス定義 ===
function getOAuthService() {
const { clientId, clientSecret } = getOAuthCredentials();
return OAuth2.createService('logiless')
.setAuthorizationBaseUrl('https://app2.logiless.com/oauth/v2/auth')
.setTokenUrl('https://app2.logiless.com/oauth2/token')
.setClientId(clientId)
.setClientSecret(clientSecret)
.setRedirectUri(ScriptApp.getService().getUrl())
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache());
}
// === コールバック処理(認証成功・失敗) ===
function authCallback(request) {
const service = getOAuthService();
const isAuthorized = service.handleCallback(request);
const message = isAuthorized
? "LOGILESS APIの認証に成功しました。"
: "LOGILESS APIの認証に失敗しました。<br>もう一度お試しください。";
if (isAuthorized) {
const token = service.getToken();
Logger.log(`🟢 初回認証トークン: ${JSON.stringify(token)}`);
saveTokenExpiry(token);
if (token.refresh_token) {
PropertiesService.getScriptProperties().setProperty('refresh_token', token.refresh_token);
}
}
showDialog(message, isAuthorized ? "認証成功" : "認証失敗");
return HtmlService.createHtmlOutput(`<p>${message}</p>`);
}
// === アクセストークンの有効期限を保存 ===
function saveTokenExpiry(token) {
if (token?.expires_in) {
const expiryTime = Date.now() + token.expires_in * 1000;
PropertiesService.getScriptProperties().setProperty('token_expiry', expiryTime.toString());
}
}
// === UIメニューからの更新実行用 ===
function refreshAccessTokenFromMenu() {
try {
refreshAccessTokenIfNeeded(true, false);
} catch (e) {
Logger.log(`⚠️ メニュー実行中のエラー: ${e.message}`);
showDialog(`アクセストークン更新に失敗しました。<br>エラー: ${e.message}`, "認証状況");
}
}
// === トリガーからの更新用 ===
function refreshAccessTokenOnTrigger() {
refreshAccessTokenIfNeeded(false, true);
}
// === 有効期限チェック&自動リフレッシュ処理 ===
function refreshAccessTokenIfNeeded(forceRefresh = false, isTriggered = false) {
const service = getOAuthService();
const props = PropertiesService.getScriptProperties();
const expiryTime = Number(props.getProperty('token_expiry')) || 0;
const remainingTime = expiryTime - Date.now();
if (!service.hasAccess()) {
return isTriggered ? null : showDialog("LOGILESS APIの認証が必要です。", "認証状況");
}
if (!expiryTime) {
Logger.log("⚠️ 有効期限が保存されていません。");
return isTriggered ? null : showDialog("トークンの有効期限が保存されていません。<br>初回認証をやり直してください。", "認証状況");
}
if (forceRefresh || remainingTime <= 24 * 60 * 60 * 1000) {
refreshAccessToken(isTriggered);
} else if (!isTriggered) {
showDialog(`残り時間: ${formatExpiryTime(expiryTime)}<br>24時間未満になったら更新します。`, "認証状況");
}
}
// === アクセストークンのリフレッシュ実行 ===
function refreshAccessToken(isTriggered = false) {
const props = PropertiesService.getScriptProperties();
const refreshToken = props.getProperty('refresh_token');
const { clientId, clientSecret } = getOAuthCredentials();
if (!refreshToken || !clientId || !clientSecret) {
return isTriggered ? null : showDialog("認証情報が不足しています。<br>初回認証をやり直してください。", "認証状況");
}
const query = `client_id=${encodeURIComponent(clientId)}&` +
`client_secret=${encodeURIComponent(clientSecret)}&` +
`grant_type=refresh_token&` +
`refresh_token=${encodeURIComponent(refreshToken)}`;
const url = `https://app2.logiless.com/oauth2/token?${query}`;
let responseText = '';
try {
const response = UrlFetchApp.fetch(url, {
method: 'get',
muteHttpExceptions: true,
});
responseText = response.getContentText();
Logger.log(`📥 APIレスポンス: ${responseText}`);
if (!responseText) {
return isTriggered ? null : showDialog("レスポンスが空です。更新失敗。", "認証状況");
}
const result = JSON.parse(responseText);
if (result.access_token) {
const newExpiryTime = Date.now() + result.expires_in * 1000;
props.setProperty('token_expiry', newExpiryTime.toString());
if (result.refresh_token) props.setProperty('refresh_token', result.refresh_token);
return isTriggered ? null : showDialog(`トークンを更新しました。<br>${formatExpiryTime(newExpiryTime)}`, "認証状況");
} else {
return isTriggered ? null : showDialog(`更新に失敗しました。<br>${responseText}`, "認証状況");
}
} catch (error) {
Logger.log(`⚠️ JSONパース失敗: ${error.message}`);
return isTriggered ? null : showDialog(`JSONエラー: ${error.message}`, "認証状況");
}
}
// === 有効期限のフォーマット表示 ===
function formatExpiryTime(expiryTime) {
const expiryDate = new Date(expiryTime);
const remainingTime = expiryTime - Date.now();
return `アクセストークンの有効期限:${expiryDate.toLocaleString()}<br>
残り ${Math.floor(remainingTime / 86400000)}日
${Math.floor((remainingTime % 86400000) / 3600000)}時間
${Math.floor((remainingTime % 3600000) / 60000)}分`;
}
// === 現在の認証状況を確認 ===
function checkAccess() {
const service = getOAuthService();
const props = PropertiesService.getScriptProperties();
const expiryTime = Number(props.getProperty('token_expiry')) || 0;
if (service.hasAccess()) {
showDialog(`認証済みです。<br>${formatExpiryTime(expiryTime)}`, "認証状況");
} else {
showDialog("LOGILESS APIの認証が必要です。", "認証状況");
}
}
// === 認証情報のリセット ===
function reset() {
getOAuthService().reset();
showDialog("認証をリセットしました。<br>初回認証からやり直してください。", "認証状況");
}
// === ダイアログ表示共通関数 ===
function showDialog(message, title = "通知") {
SpreadsheetApp.getUi().showModalDialog(
HtmlService.createHtmlOutput(`<p>${message}</p>`), title
);
}
✅ 補足:最初に呼び出す関数
認証を開始:authorize()
クライアントIDの登録:setClientCredentials()(一度だけ手動実行)
トークン有効期限チェック&更新:refreshAccessTokenFromMenu() またはトリガーで refreshAccessTokenOnTrigger()
次回予告:実際にLogiless APIを叩いてみる!
今回は「認証部分」までを紹介しましたが、次回は:
ステップ1:出荷実績 or 受注情報の取得
ステップ2:結果をスプレッドシートに書き出す
ステップ3:業務データとの突合や自動通知
など、「Logiless APIで何ができるか」具体的な活用例を深掘りしていきます。
まとめ:GAS × Logilessは社内DXの強力な武器
今回の認証構築で、「使ってみたいけど面倒そう…」という状態から一歩踏み出す準備が整いました。
次はこの認証基盤を活かして、
日次の出荷レポートの自動生成
返品状況のAPIチェック
ECサイトとの在庫連携
などなど、業務効率化・自動化の展開に進んでいきます。
それでは、また次の実験で!🦊
#ノーコード #GAS #業務自動化 #Logiless #100日チャレンジ #ChatGPTで業務効率化