# 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