※「JUST.DB」は株式会社ジャストシステムの商品です。
こんな悩み、ありませんか
- 営業が JUST.DB に新しい取引先を入れるたびに、経理が同じ会社を freee 会計にも登録している
- 表記ゆれで「株式会社ABC」と「(株)ABC」が freee に二重登録され、月末の請求でつまずく
- Slack で「あの取引先、freee に追加しといて」と経理から催促されるたびに気まずい
- 有料 iPaaS の月額数千〜数万円は払えない、稟議が通らない
JUST.DB と freee はそれぞれ便利なのに、「同じ会社情報を 2 つのシステムに入れる人」が必ず必要になります。これは業務担当者の不満が集まりやすいポイントです。
こんなことができます
JUST.DB の取引先マスタにレコードを 1 件追加すると、30 秒〜1 分ほどで freee 会計の取引先一覧にも同じ会社が自動登録される——これを 追加費用ゼロ(Google Workspace の範囲内で完結)で実現します。

中継には Google Apps Script(GAS) を使います。有料 iPaaS の追加契約は不要で、Google Workspace を契約していれば、新たに必要なものはありません。
本記事を読みながら手を動かせば、操作に慣れている方なら約 2〜4 時間で構築完了。経理の二重入力ゼロを今日中に実現できます。
構成と仕組み
Webhook → GAS → freee の流れ
JUST.DB のデータテーブルには「Webhook 設定」という機能があり、レコードの新規作成・更新・削除を任意の URL に通知できます。本構成ではその通知先を GAS の Web App URL にします。
ここで押さえておきたいポイントが 2 つあります。
ポイント① Webhook が送ってくるのは「メタデータ」だけ
JUST.DB Webhook が送るペイロードには tableName(テーブル識別名)と recordId(レコード ID)、変更日時などのメタデータが含まれます。取引先名や取引先コードといった実フィールドの値は含まれません。
そのため、GAS 側では受信した recordId をキーに JUST.DB の REST API(GET /tables/{tableName}/records/{recordId})を 1 回叩いてフィールド値を取り直す作りにします。本記事のサンプルコードはこの 2 段階処理を最初から組み込んでいます。
ポイント② Webhook は JUST.DB の画面操作で発火
業務担当者が JUST.DB の管理画面でレコードを追加・更新すると、30 秒〜1 分ほどで Webhook が発火します(実測値ベース。JUST.DB の負荷状況により前後します)。今回の用途(営業や経理が画面で取引先を登録)にはピッタリです。 JUST.DB を Salesforce や kintone 等の他システムから API 経由で投入する場合は別途 GAS の時間ベーストリガーで定期 Pull する併用構成にします(本記事末尾の「Pull 型併用」を参照)。
なぜ GAS が最適なのか
| 中継候補 | コスト | 特徴 |
|---|---|---|
| Google Apps Script | ¥0(Workspace 内で完結) | UrlFetchApp で外部 API、Sheets 連携、時間ベーストリガー全部内蔵 |
| Yoom | 月¥9,600〜(ミニプラン年契約) | UI 親切、JUST.DB コネクタあり |
| Power Automate | M365 + プレミアム | 公式 freee コネクタは記事執筆時点で未提供、HTTP コネクタはプレミアム必須 |
| make.com | 月 1,000 ops まで無料 | 最小実行間隔 15 分、アクティブ 2 シナリオまで |
| Zapier | 月 100 タスクまで無料 | 無料枠は月 100 タスク。複数連携を量産する用途は有償プラン推奨 |
中堅企業の連携トラフィック(1 日数十〜数百件)では GAS の制限(1 実行 6 分/トリガー累積 6 時間/日/UrlFetch 10 万件/日。Google Workspace 契約時の値)に余裕で収まります。
必要なもの
| 項目 | 内容 |
|---|---|
| Google Workspace アカウント | Apps Script & Sheets が使える管理者権限 |
| freee アカウント | 管理者権限(OAuth アプリ登録ができる人) |
| JUST.DB 環境 | 構築者ロールのユーザー、対象データテーブル(取引先マスタ)が作成済み |
| Node.js(任意) | clasp(CLI でデプロイしたい場合のみ) |
ステップ1 GAS プロジェクトを作成
A. clasp(CLI)で作る場合
mkdir -p justdb-freee-bridge/src && cd justdb-freee-bridge
npx @google/clasp login
npx @google/clasp create --type webapp --title "JUSTDB-freee Bridge" --rootDir ./srcB. ブラウザから作る場合
https://script.google.com/home で「新しいプロジェクト」をクリックし、プロジェクト名を JUSTDB-freee Bridge に変更します。
appsscript.json(プロジェクト設定)を以下に置き換えます。
{
"timeZone": "Asia/Tokyo",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"webapp": {
"executeAs": "USER_DEPLOYING",
"access": "ANYONE_ANONYMOUS"
},
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/script.scriptapp"
]
}ステップ2 受信〜連携のコードを実装
Code.gs(または Code.js)に以下を貼り付けます。
const PROPS = PropertiesService.getScriptProperties();
/** JUST.DB Webhook 受信エンドポイント */
function doPost(e) {
const startTime = new Date();
let payload = null;
let result = { status: 'pending' };
try {
payload = JSON.parse(e.postData.contents);
logToSheet({
executed_at: startTime, trigger_type: 'webhook', direction: 'push',
result: 'received', request_summary: summarizePayload(payload),
response_summary: '', error_detail: ''
});
let records = extractRecords(payload);
// Webhook ペイロードがメタデータのみの場合、recordId から JUST.DB API でフィールド再取得
if (records.length === 0 && payload.tableName && payload.recordId) {
const fetched = fetchJustDbRecord(payload.tableName, payload.recordId);
if (fetched.error) {
records = [];
} else {
records = [fetched.record || fetched];
}
}
const eventType = detectEventType(payload);
const results = records.map(r => {
try { return syncPartnerToFreee(r, eventType); }
catch (err) { return { ok: false, error: err.message }; }
});
result = { status: 'ok', processed: results.length, results };
logToSheet({
executed_at: new Date(), trigger_type: 'webhook', direction: 'push',
result: results.every(r => r.ok) ? 'success' : 'partial',
request_summary: `event=${eventType} records=${records.length}`,
response_summary: JSON.stringify(results).substring(0, 500),
error_detail: ''
});
} catch (err) {
result = { status: 'error', message: err.message };
logToSheet({
executed_at: new Date(), trigger_type: 'webhook', direction: 'push',
result: 'error', request_summary: payload ? summarizePayload(payload) : 'parse-failed',
response_summary: '', error_detail: err.stack || err.message
});
}
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
function doGet() {
return ContentService.createTextOutput(JSON.stringify({
service: 'JUSTDB-freee Bridge', status: 'alive', now: new Date().toISOString()
})).setMimeType(ContentService.MimeType.JSON);
}
/** JUST.DB API で recordId から完全なフィールド情報を取得 */
function fetchJustDbRecord(tableName, recordId) {
const apiKey = PROPS.getProperty('JUSTDB_API_KEY');
const baseUrl = PROPS.getProperty('JUSTDB_BASE_URL');
if (!apiKey || !baseUrl) return { error: 'JUSTDB credentials not set' };
const url = `${baseUrl}/sites/api/services/v1/tables/${tableName}/records/${recordId}`;
const res = UrlFetchApp.fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` }, muteHttpExceptions: true
});
if (res.getResponseCode() !== 200) return { error: `HTTP ${res.getResponseCode()}` };
return JSON.parse(res.getContentText());
}
/** 取引先を freee に登録 */
function syncPartnerToFreee(record, eventType) {
const accessToken = PROPS.getProperty('FREEE_ACCESS_TOKEN');
const companyId = Number(PROPS.getProperty('FREEE_COMPANY_ID'));
if (!accessToken || !companyId) return { ok: false, error: 'freee credentials not set' };
if (eventType === 'deleted') return { ok: true, skipped: 'delete event ignored' };
const code = pickField(record, ['取引先コード', 'partner_code', 'code']);
const name = pickField(record, ['取引先名', 'name']);
if (!code || !name) return { ok: false, error: 'partner_code or name is missing' };
const body = {
company_id: companyId, name: name,
name_kana: pickField(record, ['取引先名カナ', 'name_kana']) || undefined,
available: pickField(record, ['有効フラグ', 'is_active']) !== false
};
const res = UrlFetchApp.fetch(`https://api.freee.co.jp/api/1/partners`, {
method: 'post', contentType: 'application/json',
headers: { Authorization: `Bearer ${accessToken}` },
payload: JSON.stringify(body), muteHttpExceptions: true
});
const status = res.getResponseCode();
if (status >= 200 && status < 300) {
const respBody = JSON.parse(res.getContentText());
return { ok: true, freee_id: respBody.partner?.id, code, name };
}
return { ok: false, status, body: res.getContentText().substring(0, 300) };
}
function detectEventType(p) { return p.eventType || p.event || p.changeType || 'unknown'; }
function extractRecords(p) { return p.records || (p.record ? [p.record] : (p.data || [])); }
function summarizePayload(p) {
const j = JSON.stringify(p);
return j.length > 2000 ? j.substring(0, 2000) + '...' : j;
}
function pickField(record, keys) {
for (const k of keys) {
if (record[k] !== undefined) return record[k];
if (record.fields && record.fields[k] !== undefined) return record.fields[k];
}
return undefined;
}
/** ログ用 Sheets 自動作成 */
function setupProperties() {
let id = PROPS.getProperty('LOG_SHEET_ID');
if (!id) {
const ss = SpreadsheetApp.create('JUSTDB-freee Bridge - 連携ログ');
id = ss.getId();
PROPS.setProperty('LOG_SHEET_ID', id);
const sh = ss.getActiveSheet();
sh.setName('sync_logs');
sh.appendRow(['executed_at', 'trigger_type', 'direction', 'result',
'request_summary', 'response_summary', 'error_detail']);
sh.setFrozenRows(1);
console.log('Created log spreadsheet:', `https://docs.google.com/spreadsheets/d/${id}`);
}
}
function logToSheet(entry) {
const id = PROPS.getProperty('LOG_SHEET_ID');
if (!id) return;
SpreadsheetApp.openById(id).getSheetByName('sync_logs').appendRow([
Utilities.formatDate(entry.executed_at, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'),
entry.trigger_type, entry.direction, entry.result,
entry.request_summary, entry.response_summary, entry.error_detail
]);
}clasp を使う場合は clasp push --force でアップロード、ブラウザの場合はそのまま保存します。
ステップ3 スコープ承認とログ Sheets 自動生成
GAS エディタの関数選択ドロップダウンで setupProperties を選び「実行」をクリックします。

OAuth スコープ承認ダイアログが出るので「許可」をクリック。実行が完了すると、ログ用 Google Sheets が自動生成され、URL がコンソールに出力されます。

ステップ4 Web App として公開デプロイ
エディタ右上の「デプロイ」→「新しいデプロイ」をクリック。設定は以下のとおりです。
| 項目 | 値 |
|---|---|
| 種類 | ウェブアプリ |
| 次のユーザーとして実行 | 自分(デプロイ者) |
| アクセスできるユーザー | 全員 |

「デプロイ」をクリックすると Web App URL が表示されるので、コピーして控えておきます。

ブラウザでこの URL を開くと {"service":"JUSTDB-freee Bridge","status":"alive",...} と返ってくれば疎通 OK です。
ただし、ツール間連携が 1 本から 3 本・5 本と増えてくると、GAS スクリプトの管理コストが一気に膨らみます。トークンの期限切れ検知・実行ログの一元管理・エラー時の再実行——これらを GAS で自前実装するのはかなりの手間です。そこで検討したいのが iPaaS(Integration Platform as a Service)(アイパース=ツール間連携専用のプラットフォーム)への移行です。
GAS から iPaaS に移行する目安
| 状況 | 推奨 |
|---|---|
| 連携数が 1〜2 本・変更頻度が低い | GAS で十分 |
| 連携数が 3 本以上・条件分岐が複雑 | iPaaS への移行を検討 |
| エラー通知・再試行・ログ監視が必要 | iPaaS が有利 |
| 非エンジニアが業務フローを自分で変更したい | iPaaS(ノーコード UI)が最適 |
主要 iPaaS の比較でいうと、Zapier はノーコードで最も連携アプリ数が多く(7,000 以上)、Make(旧 Integromat) は複雑な分岐処理を視覚的に組めるのが強み、n8n はオープンソースでセルフホスティングも可能な柔軟性があります。JUST.DB や freee はいずれの iPaaS でも公式コネクタまたは Webhook/REST API 経由で連携できます。
デプロイの注意点
appsscript.json で webapp.access を設定済みでも、UI から「新しいデプロイ」を 1 度実行しないと「全員アクセス」設定が反映されないことがあります。clasp の clasp deploy コマンドだけで済ませず、必ず一度 UI から再デプロイしてください。
ステップ5 JUST.DB の Webhook を設定
JUST.DB の取引先管理プレートを編集モードに切り替えて、対象パネルの「パネル編集メニュー → データテーブルの設定」を開きます。
「Webhook 設定」タブで「Webhook 設定の追加」をクリック。以下を入力します。
| 項目 | 値 |
|---|---|
| タイトル | GAS Bridge to freee |
| Webhook URL | ステップ4でコピーした GAS Web App URL |
| 送信対象フィールド選択 | 全フィールド送信(既定) |
| レコードが新規作成された | ✅ チェック |
| レコードが更新された | ✅ チェック |

保存して、Webhook 一覧に登録されることを確認します。

ステップ6 JUST.DB API キーを発行
GAS から JUST.DB API でフィールド値を取得するため、API キーを 1 つ発行します。
JUST.DB 右上のユーザーメニューから「運用管理画面 → API 連携設定 → + API-Key の追加」をクリック。
| 項目 | 値 |
|---|---|
| タイトル | D0_GAS_Bridge_Test |
| 対象テーブル | 取引先マスタ |
| 実行権限 | 閲覧(追加・編集・削除はチェック不要) |
「保存」すると一度だけ API キーが表示されるので必ずコピーします。

ステップ7 freee Developer Console でアプリを登録
freee 側のトークンを発行するため、freee Developer Console(https://app.secure.freee.co.jp/developers/)でアプリを登録します。
- ログイン後、対象事業所を選択
- 「アプリ管理 → 新規追加」をクリック
- 以下を入力
| 項目 | 値 |
|---|---|
| アプリ名 | JUSTDB-freee Bridge |
| 概要 | JUST.DB と freee 会計を Google Apps Script で連携する中継基盤 |
| アプリタイプ | プライベートアプリ |
| 利用規約 | ✅ 同意 |

- 「作成」をクリックすると Client ID と Client Secret が発行されます。
- 「権限設定」タブで必要なスコープにチェックを入れて「下書き保存」。最低限必要なのは以下です。
| カテゴリ | 必要権限 |
|---|---|
| [会計] 取引先 | 参照・更新 |
| [会計] 取引 | 参照 |
| [会計] 事業所 | 参照 |
※ freee Developer Console の権限区分は 参照/更新 の2区分です(追加・変更・削除は「更新」に含まれます)。権限設定タブはスコープが多いため、画面下にスクロールすると [会計] 取引先 が表示されます。本番運用時は必要最小限のスコープのみチェックしてください。

ステップ8 freee アクセストークンを取得
ブラウザで以下の URL(Client ID 部分は自分のものに置き換え)を開きます。
https://accounts.secure.freee.co.jp/public_api/authorize?client_id={Client ID}&response_type=token&redirect_uri=urn:ietf:wg:oauth:2.0:oob
「許可する」をクリックすると遷移先 URL に下記のような形でアクセストークンが含まれた状態になります。
.../authorize/token?...#access_token=BP8...&token_type=Bearer&expires_in=21600URL バーをクリックして #access_token= の直後から次の & までをコピーすればトークン本体が取れます(token_type expires_in はトークン本体には含めません)。
事業所 ID は freee 会計にログインした状態で URL の /companies/{ID} 部分を見れば確認できます(事業所により桁数は異なります)。
本番運用上の注意
implicit flow(response_type=token)で取得したトークンは freee の仕様で 6 時間で期限切れ(Authorization Code flow と同じ)。本記事は「まず動かす」ことを優先して implicit flow を採用していますが、長期運用ではリフレッシュトークン付きの Authorization Code flow に切り替えてください。implicit flow は OAuth 2.1 で廃止方向にあるため、本番システムでは Authorization Code 一択で考えるのが安全です。
ステップ9 GAS にスクリプトプロパティを保存
GAS エディタ左サイドバーの「プロジェクトの設定 → スクリプトプロパティ」で 4 つの値を手動で追加します(LOG_SHEET_ID はステップ3の setupProperties 実行時に自動生成済みのため、合計5つ表示されている状態になります)。
| キー | 値 |
|---|---|
| FREEE_ACCESS_TOKEN | ステップ8で取得したトークン |
| FREEE_COMPANY_ID | freee 事業所 ID(数字) |
| JUSTDB_API_KEY | ステップ6で発行した API キー |
| JUSTDB_BASE_URL | 例 https://{あなたのテナント識別子}.just-db.com |

LOG_SHEET_ID を含む)ステップ10 デプロイを更新
コードや設定を変更した後は、必ず「デプロイ → デプロイを管理 → 既存デプロイの編集ボタン → 新しいバージョンに更新 → デプロイ」を実行します。
これで構築は完了です。次は実際に動かしてみましょう。
動作確認 取引先1件を入力して freee に届くまで
1. JUST.DB の取引先マスタを開く
業務プレート「取引先管理」の取引先マスタパネルを表示します。

2. 「レコードの新規作成」をクリックして入力
ツールバーの「+」アイコンをクリックして新規レコードダイアログを開き、最低限、取引先コードと取引先名を入力します。
| 取引先コード | 取引先名 |
|---|---|
| DEMO_001 | 株式会社サンプルコーポレーション |

3. 「保存して閉じる」をクリック
保存すると JUST.DB のサーバー処理が走り、その間「しばらくお待ちください…」と表示されます。完了次第、30 秒〜1 分ほどで Webhook が GAS に通知され、GAS から freee API が呼ばれます。

4. freee 会計で確認
freee 会計の「設定 → 取引先の設定」を開くと、最上段に「株式会社サンプルコーポレーション」が登録されています。

JUST.DB → freee の自動連携が動作している証拠
これで JUST.DB から freee への自動連携が動作していることが確認できました。取引先マスタを freee で運用しているチームでは、経理が freee に取引先を再入力する必要は、もうありません。
運用 Tips
| 項目 | 内容 |
|---|---|
| GAS の実行制限 | 1 実行 6 分/トリガー累積 6 時間/日/UrlFetch 10 万件/日(Google Workspace 契約時の値。詳細は本文「構成と仕組み」参照) |
| freee アクセストークン | 6 時間で期限切れ(implicit/Authorization Code 共通)。リフレッシュトークン運用が推奨 |
| Webhook 失敗時の挙動 | JUST.DB はデフォルトで再送しない。GAS は確実に 200 を返すように作る |
| ログの肥大化 | sync_logs シートが数万行になったら年単位でアーカイブ |
| 認証情報の管理 | 必ずスクリプトプロパティに保存。コード直書き禁止 |
| Webhook 第三者対策 | リクエストヘッダで X-Auth-Secret 等の共有シークレットを設定 |
Pull 型の併用(任意)
freee 側で取引先が更新された時に JUST.DB へ反映したい、または JUST.DB を API 経由で操作する別システムがある場合は、GAS の時間ベーストリガーで freee を定期的にポーリングする関数を併設します。
function pullPartnersFromFreee() {
const token = PROPS.getProperty('FREEE_ACCESS_TOKEN');
const companyId = Number(PROPS.getProperty('FREEE_COMPANY_ID'));
const url = `https://api.freee.co.jp/api/1/partners?company_id=${companyId}&limit=100`;
const res = UrlFetchApp.fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const data = JSON.parse(res.getContentText());
// ここではリスト取得のみ。JUST.DB 側のレコード更新は読者の業務要件に合わせて
// freee 連携 ID(field_xxxxxxxxxx)で照合して PUT する処理を追加してください。
return data.partners.length;
}GAS エディタの「トリガー」から pullPartnersFromFreee を 5 分間隔で実行するように設定すれば、Pull 同期の入口は完成です。詳細な双方向同期の実装は次回(取引先マスタの双方向同期)の記事で扱います。
まとめ
- JUST.DB の取引先入力を起点に freee に自動同期する基盤が 追加費用ゼロ で作れる
- Google Workspace の中で完結するので、他社の有料 iPaaS の月額費用がかからない
- Webhook ペイロードがメタデータのみという仕様に合わせ、GAS で JUST.DB API を再呼び出ししてフィールドを取得する 2 段構成が肝
- 経理の二重入力をゼロにするだけで、月末の請求作業が劇的に楽になる
本記事のアーキテクチャは、取引先マスタだけでなく 案件・請求書・経費精算 にも応用できます。次回は 案件原価管理を JUST.DB で運用しながら freee に自動仕訳する方法 を紹介します。
長期的には iPaaS へのステップアップを
今回 GAS を選んだ理由は「iPaaS(アイパース)を契約していない会社でも今日から動かせる」という手軽さにあります。Google アカウントさえあれば追加コスト ¥0 で動き、社内エンジニアがいなくても JUST.DB の Webhook 設定と freee の OAuth 取得だけで連携が完成します。
ただし、ツール間連携が 1 本から 3 本・5 本と増えてくると、GAS スクリプトの管理コストが膨らみます。トークンの期限切れ検知・実行ログの一元管理・エラー時の再実行——これらを GAS で自前実装するのは相応の手間です。そこで検討したいのが iPaaS への移行です。
GAS から iPaaS に切り替える目安
| 状況 | 推奨 |
|---|---|
| 連携数が 1〜2 本・変更頻度が低い | GAS で十分 |
| 連携数が 3 本以上・条件分岐が複雑 | iPaaS への移行を検討 |
| エラー通知・再試行・実行ログ管理が必要 | iPaaS が有利 |
| 非エンジニアが業務フローを自分で編集したい | iPaaS(ノーコード UI)が最適 |
代表的な iPaaS としては、ノーコードで連携アプリ数が最多の Zapier、視覚的なフロー設計が強みの Make(旧 Integromat)、オープンソースでセルフホスティングも可能な n8n があります。JUST.DB・freee はいずれも Webhook や REST API 経由でこれらと接続できます。
本記事の検証環境について
本記事のスクリーンショットおよび動作検証は JUST.DB の無料トライアル環境で実施しています。本番運用にあたっては、株式会社ジャストシステムとの有償ライセンス契約後に同一手順をご利用ください。freee 側も「開発用テスト事業所」を用いており、本番事業所での実装時はトークン取得や権限付与の運用ルールにご留意ください。
JUST.DB と freee の連携、自社で構築するのが難しい場合は
はてなベース株式会社では、JUST.DB の業務プレート設計から freee 等の会計 SaaS との API 連携、Google Apps Script を活用した中継基盤の構築まで一気通貫で支援しています。「自社の業務に合わせて連携を組みたいが、社内に GAS を書ける人がいない」という方はお気軽にご相談ください。
はてなベースは Zapier 公式パートナー
はてなベースは Zapier の公式パートナーとして認定されています。JUST.DB と freee の連携に限らず、社内で利用しているツール群を Zapier でつなぐ設計から構築・運用までを一気通貫でご支援できます。
「まずは GAS で動かしてみたけれど、本格運用に向けて iPaaS に乗り換えたい」「どの iPaaS が自社の業務フローに合うか判断できない」といったご相談も歓迎しています。Zapier の導入効果や費用対効果のシミュレーションも含め、無料相談からお気軽にどうぞ。