# freee API 사용법 (Google Apps Script)
## 개요
freee는 일본의 클라우드 회계 소프트웨어입니다. Google Apps Script를 통해 freee API를 연동하여 회계 데이터를 자동으로 조회하고 Google Sheets에 저장할 수 있습니다.
## API 준비사항
1. **freee 계정**: https://www.freee.co.jp/
2. **freee 앱 등록**:
- freee Developers (https://developer.freee.co.jp/)
- 새 앱 생성
- Client ID, Client Secret 발급
- Callback URL 설정: `https://script.google.com/macros/d/{SCRIPT_ID}/usercallback`
3. **OAuth2 라이브러리 설치**:
- Google Apps Script에서 OAuth2 인증을 위해 라이브러리 필요
- Library ID: `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF`
## Google Apps Script 샘플 코드
### 1. OAuth2 라이브러리 설정
```javascript
// code.gs
// Google Sheet에서 freee API 정보 읽기
function getFreeeConfig() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('API_Config');
return {
clientId: sheet.getRange('B7').getValue(), // freee Client ID
clientSecret: sheet.getRange('B8').getValue(), // freee Client Secret
companyId: sheet.getRange('B13').getValue() // 사업소ID
};
}
// OAuth2 서비스 생성
function getFreeeService() {
const config = getFreeeConfig();
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(config.clientId)
.setClientSecret(config.clientSecret)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('read write')
.setParam('response_type', 'code');
}
// OAuth 콜백 함수
function authCallback(request) {
const service = getFreeeService();
const authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('인증 성공! 이 창을 닫아도 됩니다.');
} else {
return HtmlService.createHtmlOutput('인증 실패. 다시 시도해주세요.');
}
}
// 인증 URL 가져오기
function getAuthUrl() {
const service = getFreeeService();
if (!service.hasAccess()) {
const authUrl = service.getAuthorizationUrl();
Logger.log('인증 URL: ' + authUrl);
return authUrl;
} else {
Logger.log('이미 인증되었습니다.');
return null;
}
}
// 인증 해제
function logout() {
const service = getFreeeService();
service.reset();
Logger.log('인증이 해제되었습니다.');
}
```
### 2. 기본 API 호출 함수
```javascript
// freee API 호출 기본 함수
function callFreeeAPI(endpoint, method = 'GET', payload = null) {
const service = getFreeeService();
if (!service.hasAccess()) {
Logger.log('인증이 필요합니다. getAuthUrl()을 실행하세요.');
return null;
}
const baseUrl = 'https://api.freee.co.jp/api/1';
const url = baseUrl + endpoint;
const options = {
method: method,
headers: {
'Authorization': 'Bearer ' + service.getAccessToken(),
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};
if (payload && (method === 'POST' || method === 'PUT')) {
options.payload = JSON.stringify(payload);
}
try {
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
if (statusCode === 200 || statusCode === 201) {
return JSON.parse(response.getContentText());
} else {
Logger.log('에러: ' + statusCode + ' - ' + response.getContentText());
return null;
}
} catch (error) {
Logger.log('API 호출 실패: ' + error.toString());
return null;
}
}
```
### 3. 사업소 정보 조회
```javascript
// 사업소 목록 조회
function getFreeeCompanies() {
const data = callFreeeAPI('/companies');
if (data && data.companies) {
Logger.log('사업소 목록:');
data.companies.forEach(company => {
Logger.log(`ID: ${company.id}, 이름: ${company.name}`);
});
return data.companies;
}
return null;
}
```
### 4. 거래 데이터 조회
```javascript
// 거래 목록 조회
function getFreeeDeals(startDate, endDate) {
const config = getFreeeConfig();
const companyId = config.companyId;
const endpoint = `/deals?company_id=${companyId}&start_issue_date=${startDate}&end_issue_date=${endDate}`;
const data = callFreeeAPI(endpoint);
if (data && data.deals) {
Logger.log(`${data.deals.length}건의 거래를 가져왔습니다.`);
return data.deals;
}
return null;
}
```
### 5. 계정과목 조회
```javascript
// 계정과목 목록 조회
function getFreeeAccountItems() {
const config = getFreeeConfig();
const companyId = config.companyId;
const endpoint = `/account_items?company_id=${companyId}`;
const data = callFreeeAPI(endpoint);
if (data && data.account_items) {
Logger.log(`${data.account_items.length}개의 계정과목을 가져왔습니다.`);
return data.account_items;
}
return null;
}
```
### 6. 분개 데이터 조회
```javascript
// 분개장 조회
function getFreeeJournals(startDate, endDate) {
const config = getFreeeConfig();
const companyId = config.companyId;
const endpoint = `/journals?company_id=${companyId}&start_date=${startDate}&end_date=${endDate}&download_type=csv`;
const data = callFreeeAPI(endpoint);
if (data) {
Logger.log('분개장 데이터를 가져왔습니다.');
return data;
}
return null;
}
```
### 7. 데이터를 Google Sheet에 저장
```javascript
// 거래 내역을 Sheet에 저장
function saveFreeeDealsToSheet() {
const today = new Date();
const startDate = new Date(today.getFullYear(), today.getMonth(), 1);
const endDate = today;
const startDateStr = Utilities.formatDate(startDate, 'JST', 'yyyy-MM-dd');
const endDateStr = Utilities.formatDate(endDate, 'JST', 'yyyy-MM-dd');
const deals = getFreeeDeals(startDateStr, endDateStr);
if (!deals || deals.length === 0) {
Logger.log('거래 데이터가 없습니다.');
return;
}
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('FreeeDeals');
if (!sheet) {
Logger.log('FreeeDeals 시트를 찾을 수 없습니다.');
return;
}
// 기존 데이터 삭제
const lastRow = sheet.getLastRow();
if (lastRow > 1) {
sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn()).clear();
}
// 헤더 설정
sheet.getRange(1, 1, 1, 6).setValues([
['거래ID', '발생일', '금액', '타입', '거래처', '메모']
]);
// 데이터 저장
const rows = deals.map(deal => [
deal.id,
deal.issue_date,
deal.amount,
deal.type,
deal.partner_name || '',
deal.description || ''
]);
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, 6).setValues(rows);
}
Logger.log(`${rows.length}건의 거래 내역을 저장했습니다.`);
}
// 계정과목을 Sheet에 저장
function saveFreeeAccountItemsToSheet() {
const accountItems = getFreeeAccountItems();
if (!accountItems || accountItems.length === 0) {
Logger.log('계정과목 데이터가 없습니다.');
return;
}
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('FreeeAccountItems');
if (!sheet) {
Logger.log('FreeeAccountItems 시트를 찾을 수 없습니다.');
return;
}
// 기존 데이터 삭제
const lastRow = sheet.getLastRow();
if (lastRow > 1) {
sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn()).clear();
}
// 헤더 설정
sheet.getRange(1, 1, 1, 4).setValues([
['ID', '계정과목명', '계정 분류', '세금 구분']
]);
// 데이터 저장
const rows = accountItems.map(item => [
item.id,
item.name,
item.account_category_name || '',
item.tax_code || ''
]);
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, 4).setValues(rows);
}
Logger.log(`${rows.length}개의 계정과목을 저장했습니다.`);
}
```
### 8. 자동 업데이트 설정
```javascript
// Sheet 오픈 시 메뉴 추가
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('freee')
.addItem('인증하기', 'showAuthUrl')
.addSeparator()
.addItem('거래 내역 업데이트', 'saveFreeeDealsToSheet')
.addItem('계정과목 업데이트', 'saveFreeeAccountItemsToSheet')
.addItem('전체 업데이트', 'updateAllFreeeData')
.addSeparator()
.addItem('인증 해제', 'logout')
.addToUi();
}
// 인증 URL 표시
function showAuthUrl() {
const authUrl = getAuthUrl();
if (authUrl) {
const html = HtmlService.createHtmlOutput(
`여기를 클릭하여 인증하세요
` +
`또는 이 URL을 복사하세요:
${authUrl}`
).setWidth(400).setHeight(200);
SpreadsheetApp.getUi().showModalDialog(html, 'freee 인증');
} else {
SpreadsheetApp.getUi().alert('이미 인증되었습니다.');
}
}
// 전체 데이터 업데이트
function updateAllFreeeData() {
const service = getFreeeService();
if (!service.hasAccess()) {
SpreadsheetApp.getUi().alert('먼저 인증이 필요합니다. "freee > 인증하기"를 선택하세요.');
return;
}
saveFreeeDealsToSheet();
saveFreeeAccountItemsToSheet();
SpreadsheetApp.getActiveSpreadsheet().toast('freee 데이터가 업데이트되었습니다.', '완료', 3);
}
```
### 9. 과거 분개 데이터 상태 확인
```javascript
// 분개 완료 상태 확인
function checkJournalStatus(year, month) {
const config = getFreeeConfig();
const companyId = config.companyId;
// 해당 월의 시작일과 종료일
const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
const lastDay = new Date(year, month, 0).getDate();
const endDate = `${year}-${String(month).padStart(2, '0')}-${lastDay}`;
const deals = getFreeeDeals(startDate, endDate);
if (deals) {
Logger.log(`${year}년 ${month}월: ${deals.length}건의 거래 데이터가 있습니다.`);
// 미완료 거래 확인
const incomplete = deals.filter(deal => deal.status === 'draft' || !deal.status);
if (incomplete.length > 0) {
Logger.log(`미완료 거래: ${incomplete.length}건`);
} else {
Logger.log('모든 거래가 완료되었습니다.');
}
return {
total: deals.length,
incomplete: incomplete.length,
month: `${year}-${month}`
};
}
return null;
}
// 10월분 분개 상태 확인
function check10MonthStatus() {
const status = checkJournalStatus(2025, 10);
if (status) {
const message = `${status.month}: 총 ${status.total}건, 미완료 ${status.incomplete}건`;
Logger.log(message);
SpreadsheetApp.getUi().alert(message);
}
}
```
## 주의사항
1. **OAuth2 라이브러리 필수**: Google Apps Script에서 OAuth2를 사용하려면 라이브러리 설치 필요
2. **Script ID 확인**: Callback URL 설정 시 자신의 Script ID를 사용해야 함
3. **인증 필요**: 처음 사용 시 freee 인증 필요
4. **Rate Limit**: freee API는 분당 300회 요청 제한
5. **사업소 ID**: 여러 사업소가 있는 경우 올바른 사업소 ID 사용 필요
## OAuth2 라이브러리 설치 방법
1. Google Apps Script 편집기에서 좌측 메뉴의 '라이브러리 +' 클릭
2. Script ID 입력: `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF`
3. '조회' 클릭 후 최신 버전 선택
4. '추가' 클릭
## 참고 링크
- freee API Documentation: https://developer.freee.co.jp/docs
- freee Developers: https://developer.freee.co.jp/
- OAuth2 Library: https://github.com/googleworkspace/apps-script-oauth2
- 動画で学ぶ freee API × GAS: https://developer.freee.co.jp/reference/faq/learn-by-movie