Contents
freee Developers Community の概要と利用開始手順
freee の経費精算を自動化したい場合、まずは freee Developers Community にアカウントを作成し、API 利用のためのアプリ登録を行います。開発者ポータルでは OAuth 2 系列(2024 年以降は PKCE が推奨)による認可フローが標準化されており、サンドボックス環境で安全にテストできます。本セクションでは、アカウント作成から本番利用までの全体像を俯瞰します。
- 開発者ポータルへサインアップ
- URL: https://developer.freee.co.jp/
-
企業メールアドレスで登録し、メール認証を完了させます。
-
「My Apps」から新規アプリを作成
-
後述の「アプリ登録方法」で必要項目を入力します。
-
サンドボックス環境で OAuth フローと API 呼び出しを検証
- テスト用クライアント情報は本番とは別に管理し、環境変数経由でコードに組み込みます。
アプリ登録方法
このセクションでは、freee 開発者ポータル上で 実際にアプリを作成 する手順と、設定すべき項目のポイントを解説します。
必要情報の入力例と注意点
| 項目 | 推奨設定例・ポイント |
|---|---|
| アプリ名 | 社内で一意に分かる名称(例:ExpenseAutomation-Prod) |
| 説明文 | 目的を簡潔に記載。例: 「社員の経費申請を自動化し、承認フローと領収書保存を連携」 |
| リダイレクト URI | 本番環境は https://api.example.com/oauth/callback 、テスト環境は https://sandbox-api.example.com/oauth/callback のように HTTPS を必ず使用し、環境ごとに別々に登録する |
| スコープ | 必要最小限の権限だけを付与。経費操作の場合は expenses.read expenses.write が基本です |
| Webhook 設定(任意) | 後述の「Webhook の登録手順」で別途設定可能 |
実運用向けヒント
- リダイレクト URI は本番とテストで完全に分離し、環境変数FREEE_REDIRECT_URIで管理するとミスが減ります。
- クライアントシークレットは決してコードベースにハードコーディングせず、AWS Secrets Manager・Azure Key Vault 等の安全なストレージへ保存してください。
作成後に クライアント ID と クライアントシークレット が発行されます。これらは API 呼び出し時の認可フローで必須になるため、忘れずにメモしておきましょう。
OAuth 2.1(PKCE 推奨)認可フロー
freee の認可エンドポイントは OAuth 2 系列 を採用しています。2024 年以降のガイドラインでは PKCE (Proof Key for Code Exchange) が「推奨」されており、特にサーバーレスやモバイルアプリでのセキュリティが大幅に向上します。本節ではフロー全体を図解しながら、Python と Bash の実装例を示します。
フロー概要(図)
- クライアント側で Code Verifier と Code Challenge を生成
- 認可リクエスト (
/oauth/authorize) にcode_challengeを付与してユーザーへリダイレクト - ユーザーが同意 → リダイレクト URI に
codeが返る - トークンエンドポイント (
/oauth/token) へcode_verifierと共に POST - アクセストークン取得 → API 呼び出し時の Bearer ヘッダーに設定
Code Verifier / Challenge の生成(Python)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import base64, hashlib, secrets def generate_pkce_pair() -> tuple[str, str]: verifier = secrets.token_urlsafe(64) # 43~128文字推奨 challenge = ( base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()) .rstrip(b'=').decode() ) return verifier, challenge verifier, challenge = generate_pkce_pair() print(f"Verifier: {verifier}\nChallenge: {challenge}") |
ポイント
-verifierは 43〜128 文字の URL 安全な文字列で、サーバー側に送信しません。
-challengeを認可リクエストのcode_challengeパラメータに使用します。
認可リクエスト URL(例)
|
1 2 3 4 5 6 7 8 |
https://accounts.freee.com/oauth/authorize? response_type=code& client_id={CLIENT_ID}& redirect_uri={REDIRECT_URI}& scope=expenses.read%20expenses.write& code_challenge={CHALLENGE}& code_challenge_method=S256 |
上記 URL はブラウザで開くか、フロントエンドから window.location.href に設定してください。
アクセストークン取得(cURL)
|
1 2 3 4 5 6 7 |
curl -X POST https://accounts.freee.com/oauth/token \ -d "grant_type=authorization_code" \ -d "client_id=${FREEE_CLIENT_ID}" \ -d "code=${AUTHORIZATION_CODE}" \ -d "redirect_uri=${FREEE_REDIRECT_URI}" \ -d "code_verifier=${PKCE_VERIFIER}" |
取得した access_token は 30 分(デフォルト)有効です。期限が切れたら同様のフローで再取得してください。
Expense Claims API のエンドポイントと主要パラメータ
本章では、経費精算に関わる主要 API エンドポイントを 概要説明 → パラメータ表 → 実装サンプル の順に示します。各リクエストは必ず Authorization: Bearer {TOKEN} ヘッダーを付与してください。
エンドポイント一覧
| メソッド | エンドポイント | 主な機能 |
|---|---|---|
| POST | /api/v1/expenses |
経費申請の新規作成 |
| GET | /api/v1/expenses/{id} |
指定 ID の経費詳細取得 |
| PATCH | /api/v1/expenses/{id}/approve |
承認ステータス変更(承認・却下) |
| DELETE | /api/v1/expenses/{id} |
申請の取り消し |
リクエストパラメータ例(POST)
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
company_id |
integer | ○ | 企業 ID(テナント) |
date |
string (ISO‑8601) | ○ | 経費発生日 |
amount |
integer | ○ | 金額(円) |
description |
string | ○ | 摘要 |
receipt_file_key |
string | △ | 事前にアップロードした領収書ファイルのキー |
Python サンプルコード(requests)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import os, json, requests from typing import Dict BASE_URL = "https://api.freee.co.jp/api/v1" TOKEN = os.getenv("FREEE_ACCESS_TOKEN") HEADERS = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } def _request(method: str, path: str, json_body: Dict | None = None): """共通リクエストラッパー:ステータスコードと例外処理を統一""" url = f"{BASE_URL}{path}" resp = requests.request(method, url, headers=HEADERS, json=json_body, timeout=10) # 2xx 系はそのまま JSON を返す if resp.ok: return resp.json() # エラーハンドリング(500系含む)を一元化 try: err = resp.json() except ValueError: err = {"message": resp.text} raise RuntimeError( f"[{resp.status_code}] {err.get('error', 'API Error')}: {err.get('message')}" ) # ---- 具体的操作例 ------------------------------------------------- def create_expense(): payload = { "company_id": 123, "date": "2026-07-01", "amount": 5000, "description": "交通費", "receipt_file_key": "abc123" } return _request("POST", "/expenses", payload) def get_expense(expense_id: int): return _request("GET", f"/expenses/{expense_id}") def approve_expense(expense_id: int, approver_id: int = 45): payload = {"status": "approved", "approver_id": approver_id} return _request("PATCH", f"/expenses/{expense_id}/approve", payload) def delete_expense(expense_id: int): return _request("DELETE", f"/expenses/{expense_id}") # 使用例(例外は呼び出し側で捕捉) if __name__ == "__main__": try: new = create_expense() print("Created:", json.dumps(new, ensure_ascii=False, indent=2)) fetched = get_expense(new["id"]) print("Fetched:", fetched) approved = approve_expense(new["id"]) print("Approved:", approved) except RuntimeError as e: print("API error:", e) |
主要ポイント
- タイムアウト(10 秒)を設定し、ネットワーク障害時にハングしないようにしています。
- 共通ラッパー
_requestでステータスコード別の例外処理を一本化。500 系エラーでも JSON が返らなければプレーンテキストを取得します。
Node.js(axios)サンプル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
const axios = require('axios'); require('dotenv').config(); const BASE_URL = 'https://api.freee.co.jp/api/v1'; const TOKEN = process.env.FREEE_ACCESS_TOKEN; const HEADERS = { Authorization: `Bearer ${TOKEN}` }; /** * 共通リクエスト関数:成功時は data、失敗時は例外を投げる */ async function request(method, path, body = null) { try { const cfg = { method, url: `${BASE_URL}${path}`, headers: HEADERS }; if (body) cfg.data = body; const res = await axios(cfg); return res.data; } catch (err) { // Axios は error.response が undefined のケース(ネットワーク障害)もある if (!err.response) throw new Error(`Network error: ${err.message}`); const { status, data } = err.response; throw new Error(`[${status}] ${data.error || 'API error'}: ${data.message || ''}`); } } // ---- 具体的操作 ------------------------------------------------- async function createExpense() { const payload = { company_id: 123, date: '2026-07-01', amount: 5000, description: '交通費', receipt_file_key: 'abc123' }; return request('post', '/expenses', payload); } async function getExpense(id) { return request('get', `/expenses/${id}`); } async function approveExpense(id, approverId = 45) { const payload = { status: 'approved', approver_id: approverId }; return request('patch', `/expenses/${id}/approve`, payload); } // 実行例 (async () => { try { const created = await createExpense(); console.log('Created:', created); const detail = await getExpense(created.id); console.log('Detail:', detail); const approved = await approveExpense(created.id); console.log('Approved:', approved); } catch (e) { console.error(e.message); } })(); |
エラーハンドリングのベストプラクティス
API 呼び出しは ステータスコード と レスポンスボディ の組み合わせでエラー原因を特定します。以下に代表的なケースと対策例をまとめました。
| ステータス | 主なシナリオ | 推奨対策 |
|---|---|---|
401 Unauthorized |
アクセストークンの有効期限切れ、またはスコープ不足 | リフレッシュトークンがある場合は再取得。無い場合は認可フローを最初から実行し直す |
403 Forbidden |
IP ホワイトリスト外、もしくはシークレットキー不一致 | API 呼び出し元の IP を管理画面で許可リストに追加、シークレットが正しいか再確認 |
404 Not Found |
指定 ID が存在しない、またはエンドポイントミス | リクエストパラメータをロギングして検証。URL のタイポはないかチェック |
422 Unprocessable Entity |
バリデーションエラー(必須項目欠如・形式不正) | エラーメッセージに含まれるフィールド名で入力チェックを強化 |
429 Too Many Requests |
レートリミット超過 | 指数バックオフ(例: 1 s → 2 s → 4 s …)で再試行、またはプランアップグレード |
500–504 Server Error |
freee 側の一時障害、ネットワークタイムアウト | リトライロジックを実装しつつ、障害発生時は監視ツール(Datadog, CloudWatch 等)へ通知 |
| ネットワークエラー(接続失敗・DNS エラー等) | クラウド環境の一過性トラブル | タイムアウト設定とリトライを併用し、一定回数超えたらアラート送信 |
Python での包括的リトライ例(tenacity ライブラリ使用)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import requests class ApiError(RuntimeError): pass @retry( reraise=True, stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=30), retry=retry_if_exception_type((requests.ConnectionError, requests.Timeout, ApiError)) ) def call_api(method: str, url: str, **kwargs): resp = requests.request(method, url, timeout=10, **kwargs) if resp.status_code >= 500: raise ApiError(f"Server error {resp.status_code}") elif not resp.ok: # 4xx 系は呼び出し側でハンドリングさせる raise RuntimeError(f"[{resp.status_code}] {resp.text}") return resp.json() |
このように リトライポリシー と 例外クラスの分離 を行うことで、コードが読みやすくなると同時に障害耐性が向上します。
Webhook によるリアルタイム同期
freee が提供する Webhook は、経費ステータスが変化した瞬間に外部システムへ通知できる仕組みです。2025 年に追加された expense.created と expense.approved のイベントは、承認フローの自動化で特に有用です。
Webhook 登録手順と署名検証
- Webhook 設定画面へアクセス(Developers → Webhooks)。
- 「新規登録」ボタンをクリックし、以下情報を入力。
Endpoint URL:例https://api.example.com/webhook/freee(必ず HTTPS)Signing Secret:ランダムな 32 バイト以上の文字列。保存は安全に(環境変数推奨)。- 受信サーバーで署名検証を実装し、正当性が確認できたら JSON ペイロードを処理する。
Python(Flask)での署名検証例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import os, hmac, hashlib, json from flask import Flask, request, abort app = Flask(__name__) SIGNING_SECRET = os.getenv("FREEE_WEBHOOK_SECRET") def verify_signature(payload: bytes, signature: str) -> bool: expected = hmac.new( SIGNING_SECRET.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) @app.route("/webhook/freee", methods=["POST"]) def freee_webhook(): sig = request.headers.get("X-Freee-Signature") if not sig or not verify_signature(request.data, sig): abort(400, "Invalid signature") event = json.loads(request.data) # ここで event["type"] に応じたロジックを呼び出す handle_event(event) return "", 204 def handle_event(event: dict): etype = event.get("type") if etype == "expense.created": process_created(event["data"]) elif etype == "expense.approved": process_approved(event["data"]) |
Node.js(Express)での署名検証例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
const crypto = require('crypto'); const express = require('express'); require('dotenv').config(); const app = express(); app.use(express.raw({ type: 'application/json' })); // raw body が必要 const SECRET = process.env.FREEE_WEBHOOK_SECRET; function verifySignature(rawBody, signature) { const expected = crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } app.post('/webhook/freee', (req, res) => { const sig = req.header('X-Freee-Signature'); if (!sig || !verifySignature(req.body, sig)) { return res.status(400).send('Invalid signature'); } const event = JSON.parse(req.body); switch (event.type) { case 'expense.created': // 任意の処理 break; case 'expense.approved': // 任意の処理 break; } res.status(204).end(); }); |
実装例:expense.created → Google Drive に領収書保存
|
1 2 3 4 5 6 7 |
def process_created(data): file_key = data["receipt_file_key"] # 1. freee のファイル取得 API (GET /files/{file_key}) file_resp = call_api("GET", f"/files/{file_key}") # 2. Google Drive SDK でアップロード(省略) upload_to_drive(file_resp["content"], filename=f"{data['id']}.pdf") |
ノーコード・ローコード連携例と実装サンプル
プログラミングに不慣れな担当者でも、Zapier や Make (Integromat) を活用すれば freee の経費データを他サービスへ自動同期 できます。ここでは代表的な 3 パターンと、設定時のポイントを解説します。
パターン 1:Zapier → Google Drive & kintone
| 手順 | 内容 |
|---|---|
| Trigger | New Expense (freee) – 新規経費が作成されたら発火 |
| Action 1 | Upload File (Google Drive) – receipt_file_key を使って領収書バイナリを取得し、expenses/{year}/{month} に保存 |
| Action 2 | Create Record (kintone) – 金額・日付・説明を kintone の経費管理アプリに登録 |
ポイント
- Zapier の「File」フィールドは Base64 エンコードされたバイナリ が必要です。Get File アクションで取得した content を直接渡すと OK。
- フィールドマッピングは「金額 → 金額(数値)」のように型を合わせておくとエラーが減ります。
パターン 2:Make (Integromat) → Slack & Outlook
- Webhook モジュールで freee の
expense.approvedを受信。 - HTTP リクエストモジュールで
/expenses/{id}を取得し、詳細情報を取得。 - Slack メッセージ送信 – 承認完了通知を担当者チャンネルへ。
- Outlook カレンダー作成 – 「経費承認」イベントを自動登録。
ポイント
- Make の「Retry on failure」を有効にすると、API が 429 や 500 系で失敗した場合に自動リトライが走ります。
- 各モジュールの エラーハンドリングブランチ を作成し、失敗時は別途メールで管理者へ通知させると運用が楽です。
| 手順 | 内容 |
|---|---|
| Trigger | When an HTTP request is received – freee の Webhook URL を設定 |
| Condition | イベントタイプが expense.created か判定 |
| Action (Teams) | Post a message – 「新規経費申請が届きました」メッセージを送信 |
| Action (SharePoint) | Create file – 領収書 PDF を SharePoint ライブラリに保存 |
ポイント
- Power Automate の「HTTP with Azure AD」コネクタは、Bearer トークン取得ロジックが組み込み済みなので、別途スクリプトを書く必要がありません。
テスト環境(Sandbox)での検証から本番移行、セキュリティと ROI の測定
実装後は必ず sandbox 環境 で動作確認し、本番切替時のリスクを最小化します。本章では手順・チェックリスト・セキュリティベストプラクティス、そして導入効果の定量的評価方法を示します。
Sandbox でのエンドポイント検証フロー
- サンドボックス用アカウント作成(開発者ポータルの「Sandbox」タブ)。
- リダイレクト URI にテスト用ドメイン(例:
https://sandbox-api.example.com/oauth/callback)を登録。 -
PKCE フローでアクセストークン取得し、以下シナリオを順に実行:
-
POST /expensesで 架空の申請(金額 10,000 円) GET /expenses/{id}で作成結果確認PATCH /expenses/{id}/approveで承認シミュレーション-
DELETE /expenses/{id}で削除 -
エラーレスポンス(
422,429,500)を意図的に発生させ、ハンドリングロジックが期待通り動くか確認。
本番移行チェックリスト
| 項目 | 確認ポイント |
|---|---|
| クライアント情報 | 本番用 Client ID/Secret が環境変数に正しく設定されているか |
| リダイレクト URI | 本番 URL が Developer Console に登録済みで、HTTPS が有効か |
| スコープ | 必要最小限の権限だけが付与されているか(過剰権限はセキュリティリスク) |
| Webhook エンドポイント | 公開 HTTPS、TLS 1.2 以上、署名検証ロジックが本番環境でも動作するか |
| テストカバレッジ | 作成・取得・承認・削除の全シナリオを自動テストで網羅しているか |
| ログ & モニタリング | API 失敗時に CloudWatch/Datadog にアラートが届く設定になっているか |
| IP ホワイトリスト(利用可能なら) | 社内固定 IP のみ許可し、外部からの不正アクセスを防止できているか |
トークン管理・IP 制限などのベストプラクティス
- アクセストークンは暗号化保存:AWS KMS で暗号化した上で Secrets Manager に格納。
- リフレッシュトークンは短命に:取得後 30 日以内に再認可させ、長期保存は避ける。
- IP ホワイトリスト:freee の管理画面から API 呼び出し元 IP を限定できる場合は必ず設定。
- PCI DSS 配慮:領収書画像にカード情報が含まれる可能性があるなら、保存先(Google Drive, SharePoint 等)で暗号化・アクセス権最小化を徹底。
ROI(投資対効果)の測定例
| 指標 | 現行プロセス(手作業) | API 連携後 | 改善率 |
|---|---|---|---|
| 平均処理時間 | 12 分/件 | 3 分/件 | 75 % 短縮 |
| ヒューマンエラー件数 | 8 件/月 | 1 件/月 | 87.5 % 減少 |
| 月間作業工数 | 40 人時 | 10 人時 | 75 % 削減 |
シミュレーション(中小企業・従業員 20 名の場合)
- 年間削減工数:30 時間 × 12 = 360 人時
- 平均給与:¥3,000/時間 → 年間コスト削減 ¥1,080,000
- 初期導入費用(開発・ツールライセンス):¥500,000
- 回収期間= 初期投資 ÷ 年間節約額 ≈ 0.5 年
このように、API とノーコードツールの組み合わせだけで半年以内に投資を回収できるケースが多く見込めます。
まとめと次のステップ
- 開発者ポータルでアプリ登録し、PKCE 推奨フローでアクセストークン取得。
- Expense Claims API を使って経費作成・承認・削除を自動化。エラーハンドリングは 4xx と 5xx 両方に対応することが重要です。
- Webhook によりリアルタイム通知を取得し、Google Drive や Slack などの外部サービスと連携。署名検証は必須です。
- サンドボックスでテスト → 本番環境へ安全に移行。IP 制限・トークン暗号化などのセキュリティ対策を忘れずに実装。
- ノーコードツール(Zapier、Make、Power Automate)で非エンジニアでも拡張可能なフローを構築し、ROI を定量的に評価。
最後に:freee の API は頻繁にバージョンアップが行われます。実装前後は必ず公式リファレンス(https://developer.freee.co.jp/)と「変更通知」メールをチェックし、仕様変更に備えてコードのモジュール化・テスト自動化を推奨します。
参考リンク
- freee Developers – OAuth 2.1 ドキュメント: https://developer.freee.co.jp/docs/oauth
- API リファレンス(Expense Claims): https://developer.freee.co.jp/api-docs/expenses
- Webhook 署名検証ガイド: https://developer.freee.co.jp/docs/webhooks
- PKCE 公式仕様(RFC 7636): https://www.rfc-editor.org/rfc/rfc7636.html
以上が、freee Developers Community を活用した経費精算自動化の包括的ガイドです。ぜひ本手順を参考に、業務効率化とコスト削減を実現してください。