상세 컨텐츠

본문 제목

영수증 AI 자동 정리|사진을 구글 시트 지출 내역으로 저장하기

AI 활용 사례 & 응용 🚀

by AI JB 2026. 7. 28. 17:12

본문

안녕하세요 IT 세상입니다.

이번 글에서는 영수증 사진이나 PDF 파일을 구글 드라이브 폴더에 넣으면 AI가 구매 날짜, 상호명, 총금액, 결제수단과 지출 카테고리를 읽어 구글 스프레드시트에 자동으로 정리하는 방법을 알아보겠습니다.

무료 방법은 Google Apps Script와 Gemini API 무료 티어를 사용합니다. 코드를 직접 관리하기 어렵다면 Make와 OpenAI를 연결하는 유료 방법도 함께 따라 할 수 있습니다.

이 글에서 완성할 자동화
  • 휴대전화로 영수증을 촬영합니다.
  • 사진을 지정된 구글 드라이브 폴더에 넣습니다.
  • 자동화가 15분마다 새 파일을 확인합니다.
  • AI가 구매 날짜, 상호명, 총액과 결제수단을 읽습니다.
  • 식비·교통·쇼핑·업무 등으로 지출을 분류합니다.
  • 결과를 구글 스프레드시트에 한 줄로 저장합니다.
  • 성공한 파일은 처리 완료 폴더로 이동합니다.
  • 처리하지 못한 파일은 오류 폴더로 이동합니다.

핵심 요약

  • 무료 방법: Google Drive, Apps Script, Gemini API
  • 유료 방법: Make Core, Make AI Agent (New), OpenAI API
  • 무료 지원 파일: JPG, PNG, PDF
  • 무료 코드 파일 제한: 파일 한 개당 12MB
  • 자동 실행: 15분마다 입력 폴더 확인
  • 저장 결과: 구매일, 상호명, 총액, 통화, 세액, 결제수단, 카테고리
  • 예상 설정 시간: 무료 약 35~50분, 유료 약 30~45분
AI가 읽은 숫자를 그대로 회계 처리하지 마세요.

사진이 흐리거나 빛이 반사되면 AI가 숫자를 잘못 읽을 수 있습니다. 총액, 구매일, 통화는 반드시 원본 영수증과 비교해야 합니다.

준비물

무료 방법 준비물

  • 개인 구글 계정
  • Google Drive
  • Google Sheets
  • Google Apps Script
  • Google AI Studio에서 생성한 Gemini API 키
  • 개인정보가 적은 시험용 영수증 사진

유료 방법 준비물

  • 구글 계정
  • Google Drive와 Google Sheets
  • Make Core 요금제
  • Make AI Agent (New)
  • OpenAI 개발자 계정과 API 결제 설정
  • 시험용 JPG 또는 PNG 영수증
Make AI Agent (New)는 현재 오픈 베타입니다.

화면의 메뉴 이름, 지원 모델과 크레딧 계산 방식은 업데이트에 따라 달라질 수 있습니다. 실제 설정 화면에 표시되는 항목과 비용을 다시 확인하세요.

OCR은 사진 속 글자를 컴퓨터가 읽을 수 있는 데이터로 바꾸는 기술입니다.

이번 자동화에서는 별도의 OCR 프로그램을 추가하지 않고, 이미지와 PDF를 이해할 수 있는 AI 모델이 영수증 내용을 직접 분석합니다.

완성되는 자동화의 흐름

영수증 촬영 구글 드라이브 업로드 AI 분석 구글 시트 기록 완료 폴더 이동
저장 항목 내용
구매일 영수증에 표시된 실제 결제 날짜
상호명 가게·병원·호텔·판매처 등의 이름
총액 최종적으로 결제한 금액
통화 KRW, USD, JPY 등의 통화 코드
세액 부가세나 세금이 따로 표시된 경우 해당 금액
결제수단 카드, 현금, 간편결제 또는 확인 필요
카테고리 식비, 교통, 쇼핑, 업무, 의료, 교육, 숙박, 기타
품목 요약 영수증에서 확인되는 주요 구매 품목
인식 신뢰도 높음, 보통, 낮음
검토 상태 검토 필요, 검토 완료, 수정 필요

무료 방법: Apps Script와 Gemini로 만들기

무료 방법에서는 Apps Script가 구글 드라이브의 입력 폴더를 15분마다 확인합니다.

새로운 JPG, PNG 또는 PDF가 발견되면 Gemini가 파일을 분석하고 결과를 구글 스프레드시트에 기록합니다.

1단계. 지출 내역 스프레드시트 만들기

  1. 구글 드라이브에 접속합니다.
  2. 왼쪽 위의 새로 만들기를 클릭합니다.
  3. Google 스프레드시트를 클릭합니다.
  4. 파일 이름을 AI 영수증 지출 관리로 변경합니다.
여기까지 완료했다면

스프레드시트 왼쪽 위에 ‘AI 영수증 지출 관리’라는 이름이 표시됩니다.

2단계. Apps Script 열기

  1. 스프레드시트 상단의 확장 프로그램을 클릭합니다.
  2. Apps Script를 클릭합니다.
  3. 기본으로 들어 있는 코드를 모두 지웁니다.
  4. 프로젝트 이름을 AI 영수증 자동화로 변경합니다.
여기까지 완료했다면

Apps Script 편집기에 비어 있는 코드 입력창이 표시됩니다.

3단계. 전체 코드 붙여 넣기

아래 코드를 처음부터 끝까지 복사해 Apps Script 편집기에 붙여 넣습니다.

영수증 AI 자동 정리 전체 코드
const CONFIG = Object.freeze({
  MODEL: 'gemini-3.5-flash-lite',
  API_KEY_PROPERTY: 'GEMINI_API_KEY',
  INPUT_FOLDER_PROPERTY: 'RECEIPT_INPUT_FOLDER_ID',
  PROCESSED_FOLDER_PROPERTY: 'RECEIPT_PROCESSED_FOLDER_ID',
  ERROR_FOLDER_PROPERTY: 'RECEIPT_ERROR_FOLDER_ID',
  INPUT_FOLDER_NAME: 'AI_영수증_입력',
  PROCESSED_FOLDER_NAME: 'AI_영수증_처리완료',
  ERROR_FOLDER_NAME: 'AI_영수증_오류',
  SHEET_NAME: '지출내역',
  MAX_FILES_PER_RUN: 5,
  MAX_FILE_SIZE_BYTES: 12 * 1024 * 1024,
  SUPPORTED_MIME_TYPES: Object.freeze([
    'image/jpeg',
    'image/png',
    'application/pdf'
  ]),
  ALLOWED_CATEGORIES: Object.freeze([
    '식비', '교통', '쇼핑', '업무',
    '의료', '교육', '숙박', '기타'
  ])
});

function setupReceiptAutomation() {
getOrCreateFolderId_(
CONFIG.INPUT_FOLDER_PROPERTY,
CONFIG.INPUT_FOLDER_NAME
);

getOrCreateFolderId_(
CONFIG.PROCESSED_FOLDER_PROPERTY,
CONFIG.PROCESSED_FOLDER_NAME
);

getOrCreateFolderId_(
CONFIG.ERROR_FOLDER_PROPERTY,
CONFIG.ERROR_FOLDER_NAME
);

ensureExpenseSheet_();
deleteExistingTrigger_();

ScriptApp.newTrigger('processReceiptFiles')
.timeBased()
.everyMinutes(15)
.create();

SpreadsheetApp
.getActiveSpreadsheet()
.toast(
'영수증 폴더와 15분 자동 실행 설정을 만들었습니다.',
'설정 완료',
6
);
}

function processReceiptFiles() {
const lock =
LockService.getScriptLock();

if (!lock.tryLock(10000)) {
return;
}

try {
const folders =
getReceiptFolders_();

```
const fileIds =
  collectFileIds_(
    folders.input,
    CONFIG.MAX_FILES_PER_RUN
  );

fileIds.forEach(function(fileId) {
  processSingleReceipt_(
    fileId,
    folders.processed,
    folders.error
  );
});
```

} finally {
lock.releaseLock();
}
}

function processSingleReceipt_(
fileId,
processedFolder,
errorFolder
) {
const file =
DriveApp.getFileById(fileId);

const mimeType =
file.getMimeType();

try {
if (
CONFIG.SUPPORTED_MIME_TYPES
.indexOf(mimeType) === -1
) {
appendErrorRow_(
file,
'JPG, PNG, PDF 파일만 지원합니다.'
);

```
  safeMoveTo_(file, errorFolder);
  return;
}

if (
  file.getSize() >
  CONFIG.MAX_FILE_SIZE_BYTES
) {
  appendErrorRow_(
    file,
    '파일 크기가 12MB를 초과했습니다.'
  );

  safeMoveTo_(file, errorFolder);
  return;
}

const result =
  analyzeReceiptWithGemini_(file);

if (!result.valid) {
  appendErrorRow_(
    file,
    result.reason ||
    '영수증 내용을 확인하지 못했습니다.'
  );

  safeMoveTo_(file, errorFolder);
  return;
}

appendExpenseRow_(file, result);
safeMoveTo_(file, processedFolder);
```

} catch (error) {
appendErrorRow_(file, error.message);
safeMoveTo_(file, errorFolder);
}
}

function analyzeReceiptWithGemini_(file) {
const apiKey =
PropertiesService
.getScriptProperties()
.getProperty(
CONFIG.API_KEY_PROPERTY
);

if (!apiKey) {
throw new Error(
'GEMINI_API_KEY가 없습니다.'
);
}

const blob =
file.getBlob();

const base64Data =
Utilities.base64Encode(
blob.getBytes()
);

const prompt = [
'너는 영수증 지출 내역을 정리하는 담당자다.',
'첨부 파일에서 실제로 보이는 내용만 사용한다.',
'흐리거나 확인되지 않는 글자는 추측하지 않는다.',
'',
'[보안 규칙]',
'카드번호 전체, 승인번호, 회원번호, 전화번호를 출력하지 않는다.',
'',
'[분석 규칙]',
'1. 영수증이나 결제 증빙이 아니면 valid를 false로 한다.',
'2. 구매일은 YYYY-MM-DD 형식으로 작성한다.',
'3. 구매일을 확인할 수 없으면 빈 문자열로 작성한다.',
'4. total_amount는 최종 결제 금액을 숫자로 작성한다.',
'5. 통화는 KRW, USD, JPY 같은 코드로 작성한다.',
'6. 다른 통화로 환산하지 않는다.',
'7. 세액이 표시되지 않으면 tax_amount를 null로 작성한다.',
'8. 카테고리는 허용된 항목 중 하나만 선택한다.',
'9. 신뢰도는 높음, 보통, 낮음 중 하나만 선택한다.'
].join('\n');

const schema = {
type: 'object',
additionalProperties: false,
properties: {
valid: {
type: 'boolean'
},
reason: {
type: 'string'
},
purchase_date: {
type: 'string'
},
merchant: {
type: 'string'
},
total_amount: {
type: ['number', 'null']
},
currency: {
type: 'string'
},
tax_amount: {
type: ['number', 'null']
},
payment_method: {
type: 'string'
},
category: {
type: 'string',
enum:
CONFIG.ALLOWED_CATEGORIES.slice()
},
items: {
type: 'array',
maxItems: 10,
items: {
type: 'object',
additionalProperties: false,
properties: {
name: {
type: 'string'
},
amount: {
type: ['number', 'null']
}
},
required: [
'name',
'amount'
]
}
},
confidence: {
type: 'string',
enum: [
'높음',
'보통',
'낮음'
]
},
notes: {
type: 'string'
}
},
required: [
'valid',
'reason',
'purchase_date',
'merchant',
'total_amount',
'currency',
'tax_amount',
'payment_method',
'category',
'items',
'confidence',
'notes'
]
};

const apiUrl =
'https://generativelanguage.googleapis.com/' +
'v1beta/models/' +
CONFIG.MODEL +
':generateContent';

const payload = {
contents: [
{
role: 'user',
parts: [
{
inlineData: {
mimeType:
mimeTypeForGemini_(
file.getMimeType()
),
data:
base64Data
}
},
{
text: prompt
}
]
}
],
generationConfig: {
temperature: 0,
maxOutputTokens: 1200,
responseFormat: {
text: {
mimeType:
'application/json',
schema: schema
}
}
}
};

const response =
UrlFetchApp.fetch(
apiUrl,
{
method: 'post',
contentType:
'application/json',
headers: {
'x-goog-api-key':
apiKey
},
payload:
JSON.stringify(payload),
muteHttpExceptions:
true
}
);

const statusCode =
response.getResponseCode();

const responseText =
response.getContentText();

if (statusCode !== 200) {
throw new Error(
'Gemini API 오류 ' +
statusCode +
': ' +
responseText.substring(
0,
500
)
);
}

const data =
JSON.parse(responseText);

const parts =
data.candidates &&
data.candidates[0] &&
data.candidates[0].content &&
data.candidates[0].content.parts;

if (
!parts ||
parts.length === 0
) {
throw new Error(
'Gemini 응답에서 영수증 결과를 찾지 못했습니다.'
);
}

const rawResult =
parts
.map(function(part) {
return part.text || '';
})
.join('')
.trim();

return normalizeReceiptResult_(
JSON.parse(
removeCodeFence_(rawResult)
)
);
}

function mimeTypeForGemini_(mimeType) {
if (
CONFIG.SUPPORTED_MIME_TYPES
.indexOf(mimeType) === -1
) {
throw new Error(
'지원하지 않는 MIME 형식입니다: ' +
mimeType
);
}

return mimeType;
}

function normalizeReceiptResult_(result) {
return {
valid:
result.valid === true,

```
reason:
  String(
    result.reason || ''
  ).trim(),

purchaseDate:
  String(
    result.purchase_date || ''
  ).trim(),

merchant:
  String(
    result.merchant ||
    '확인 필요'
  ).trim(),

totalAmount:
  normalizeAmount_(
    result.total_amount
  ),

currency:
  String(
    result.currency ||
    '확인 필요'
  )
    .trim()
    .toUpperCase(),

taxAmount:
  normalizeAmount_(
    result.tax_amount
  ),

paymentMethod:
  String(
    result.payment_method ||
    '확인 필요'
  ).trim(),

category:
  normalizeCategory_(
    result.category
  ),

items:
  normalizeItems_(
    result.items
  ),

confidence:
  normalizeConfidence_(
    result.confidence
  ),

notes:
  String(
    result.notes || ''
  ).trim()
```

};
}

function normalizeAmount_(value) {
if (
value === null ||
value === undefined ||
value === ''
) {
return '';
}

const number =
Number(value);

return Number.isFinite(number)
? number
: '';
}

function normalizeCategory_(category) {
const text =
String(
category || ''
).trim();

return (
CONFIG.ALLOWED_CATEGORIES
.indexOf(text) !== -1
? text
: '기타'
);
}

function normalizeConfidence_(confidence) {
const text =
String(
confidence || ''
).trim();

return [
'높음',
'보통',
'낮음'
].indexOf(text) !== -1
? text
: '낮음';
}

function normalizeItems_(items) {
if (!Array.isArray(items)) {
return [];
}

return items
.slice(0, 10)
.map(function(item) {
return {
name:
String(
item &&
item.name
? item.name
: ''
).trim(),

```
    amount:
      normalizeAmount_(
        item &&
        item.amount
      )
  };
})
.filter(function(item) {
  return item.name !== '';
});
```

}

function appendExpenseRow_(
file,
result
) {
const sheet =
ensureExpenseSheet_();

const itemSummary =
result.items.length > 0
? result.items
.map(function(item) {
return (
item.name +
(
item.amount !== ''
? ' (' +
item.amount +
')'
: ''
)
);
})
.join(', ')
: '확인 필요';

sheet.appendRow([
new Date(),
result.purchaseDate,
result.merchant,
result.totalAmount,
result.currency,
result.taxAmount,
result.paymentMethod,
result.category,
itemSummary,
result.confidence,
'검토 필요',
file.getName(),
'',
result.notes
]);

const row =
sheet.getLastRow();

sheet
.getRange(row, 4)
.setNumberFormat('#,##0.##');

sheet
.getRange(row, 6)
.setNumberFormat('#,##0.##');

sheet
.getRange(row, 13)
.setFormula(
'=HYPERLINK("' +
file.getUrl() +
'","원본 보기")'
);

sheet
.getRange(
row,
1,
1,
14
)
.setVerticalAlignment('top')
.setWrap(true);
}

function appendErrorRow_(
file,
message
) {
const sheet =
ensureExpenseSheet_();

sheet.appendRow([
new Date(),
'',
'',
'',
'',
'',
'',
'',
'',
'낮음',
'수정 필요',
file.getName(),
'',
message
]);

const row =
sheet.getLastRow();

sheet
.getRange(row, 13)
.setFormula(
'=HYPERLINK("' +
file.getUrl() +
'","원본 보기")'
);

sheet
.getRange(
row,
1,
1,
14
)
.setVerticalAlignment('top')
.setWrap(true);
}

function ensureExpenseSheet_() {
const spreadsheet =
SpreadsheetApp
.getActiveSpreadsheet();

let sheet =
spreadsheet.getSheetByName(
CONFIG.SHEET_NAME
);

if (!sheet) {
sheet =
spreadsheet.insertSheet(
CONFIG.SHEET_NAME
);
}

const headers = [
'처리시간',
'구매일',
'상호명',
'총액',
'통화',
'세액',
'결제수단',
'카테고리',
'품목요약',
'인식신뢰도',
'검토상태',
'원본파일명',
'원본보기',
'메모'
];

if (sheet.getLastRow() === 0) {
sheet
.getRange(
1,
1,
1,
headers.length
)
.setValues([headers]);
}

sheet.setFrozenRows(1);

sheet
.getRange(
1,
1,
1,
headers.length
)
.setFontWeight('bold')
.setBackground('#dbeafe');

const widths = [
150,
110,
220,
120,
90,
120,
130,
100,
320,
110,
120,
230,
120,
300
];

widths.forEach(
function(width, index) {
sheet.setColumnWidth(
index + 1,
width
);
}
);

sheet
.getRange('A:A')
.setNumberFormat(
'yyyy-mm-dd hh:mm'
);

const validation =
SpreadsheetApp
.newDataValidation()
.requireValueInList(
[
'검토 필요',
'검토 완료',
'수정 필요'
],
true
)
.setAllowInvalid(false)
.build();

sheet
.getRange(
2,
11,
Math.max(
sheet.getMaxRows() - 1,
1
),
1
)
.setDataValidation(
validation
);

return sheet;
}

function collectFileIds_(
folder,
limit
) {
const files =
folder.getFiles();

const ids = [];

while (
files.hasNext() &&
ids.length < limit
) {
ids.push(
files.next().getId()
);
}

return ids;
}

function getReceiptFolders_() {
const properties =
PropertiesService
.getScriptProperties();

const inputId =
properties.getProperty(
CONFIG.INPUT_FOLDER_PROPERTY
);

const processedId =
properties.getProperty(
CONFIG.PROCESSED_FOLDER_PROPERTY
);

const errorId =
properties.getProperty(
CONFIG.ERROR_FOLDER_PROPERTY
);

if (
!inputId ||
!processedId ||
!errorId
) {
throw new Error(
'폴더 설정이 없습니다. ' +
'setupReceiptAutomation을 먼저 실행하세요.'
);
}

return {
input:
DriveApp.getFolderById(
inputId
),

```
processed:
  DriveApp.getFolderById(
    processedId
  ),

error:
  DriveApp.getFolderById(
    errorId
  )
```

};
}

function getOrCreateFolderId_(
propertyName,
folderName
) {
const properties =
PropertiesService
.getScriptProperties();

const savedId =
properties.getProperty(
propertyName
);

if (savedId) {
try {
DriveApp
.getFolderById(savedId)
.getName();

```
  return savedId;
} catch (error) {
  properties.deleteProperty(
    propertyName
  );
}
```

}

const existing =
DriveApp.getFoldersByName(
folderName
);

const folder =
existing.hasNext()
? existing.next()
: DriveApp.createFolder(
folderName
);

properties.setProperty(
propertyName,
folder.getId()
);

return folder.getId();
}

function safeMoveTo_(
file,
folder
) {
try {
file.moveTo(folder);
} catch (error) {
console.log(
'파일 이동 실패: ' +
error.message
);
}
}

function removeCodeFence_(text) {
return String(text || '')
.replace(/^`json/i, '')
    .replace(/^`/i, '')
.replace(/```$/i, '')
.trim();
}

function deleteExistingTrigger_() {
ScriptApp
.getProjectTriggers()
.forEach(function(trigger) {
if (
trigger.getHandlerFunction() ===
'processReceiptFiles'
) {
ScriptApp.deleteTrigger(
trigger
);
}
});
}

코드를 붙여 넣은 뒤 위쪽의 디스크 모양 저장 버튼을 클릭합니다.

여기까지 완료했다면

코드가 저장되고 편집기 왼쪽에 빨간 오류 표시가 나타나지 않습니다.

4단계. Gemini API 키 만들기

  1. Google AI Studio를 엽니다.
  2. 자동화에 사용할 구글 계정으로 로그인합니다.
  3. API Keys 또는 Get API key 메뉴를 엽니다.
  4. 새 키가 필요하면 Create API key를 클릭합니다.
  5. 생성된 키를 복사합니다.
API 키는 비밀번호와 같습니다.

API 키를 블로그 코드, 캡처 이미지 또는 공개 문서에 넣지 마세요.

여기까지 완료했다면

Google AI Studio에 복사할 수 있는 API 키가 표시됩니다.

5단계. API 키를 스크립트 속성에 저장하기

  1. Apps Script로 돌아갑니다.
  2. 왼쪽의 톱니바퀴 모양 프로젝트 설정을 클릭합니다.
  3. 스크립트 속성을 찾습니다.
  4. 스크립트 속성 추가를 클릭합니다.
  5. 속성 이름에 GEMINI_API_KEY를 입력합니다.
  6. 값에 복사한 API 키를 붙여 넣습니다.
  7. 스크립트 속성 저장을 클릭합니다.
여기까지 완료했다면

스크립트 속성 목록에 GEMINI_API_KEY가 표시됩니다.

6단계. 폴더와 자동 실행 설정 만들기

  1. Apps Script의 편집기로 돌아갑니다.
  2. 함수 선택 메뉴에서 setupReceiptAutomation을 선택합니다.
  3. 실행을 클릭합니다.
  4. 권한 검토를 클릭합니다.
  5. 사용할 구글 계정을 선택합니다.
  6. 드라이브, 스프레드시트와 외부 서비스 연결 권한을 확인합니다.
  7. 허용을 클릭합니다.
여기까지 완료했다면

구글 드라이브에 AI_영수증_입력, AI_영수증_처리완료, AI_영수증_오류 폴더가 생성됩니다.

스프레드시트에는 지출내역 시트가 생성됩니다.

7단계. 시험용 영수증 준비하기

  1. 영수증을 평평한 곳에 놓습니다.
  2. 빛이 반사되지 않게 촬영합니다.
  3. 영수증 위쪽과 아래쪽이 잘리지 않게 찍습니다.
  4. 불필요한 주변 배경은 잘라냅니다.
  5. JPG 또는 PNG 형식으로 저장합니다.
아이폰 HEIC 파일은 먼저 변환하세요.

이번 무료 코드는 JPG, PNG와 PDF만 처리합니다. HEIC 사진은 JPG로 변환하거나 화면을 캡처한 뒤 사용하세요.

8단계. 입력 폴더에 영수증 올리기

  1. 구글 드라이브에서 AI_영수증_입력 폴더를 엽니다.
  2. 새로 만들기를 클릭합니다.
  3. 파일 업로드를 선택합니다.
  4. 시험용 영수증을 업로드합니다.
여기까지 완료했다면

입력 폴더에 JPG, PNG 또는 PDF 파일이 표시됩니다.

9단계. 자동화를 수동으로 시험하기

  1. Apps Script로 돌아갑니다.
  2. 함수 선택 메뉴에서 processReceiptFiles를 선택합니다.
  3. 실행을 클릭합니다.
  4. 실행이 끝나면 스프레드시트의 지출내역 시트를 엽니다.
정상 작동 결과

구매일, 상호명, 총액, 통화, 결제수단과 카테고리가 두 번째 줄에 표시됩니다.

성공한 원본 파일은 AI_영수증_처리완료 폴더로 이동합니다.

10단계. 원본과 분석 결과 비교하기

  1. 원본보기 링크를 클릭합니다.
  2. 구매일을 확인합니다.
  3. 최종 결제 금액을 확인합니다.
  4. 통화와 세액을 확인합니다.
  5. 결제수단과 카테고리를 확인합니다.
  6. 틀린 값은 시트에서 직접 수정합니다.
  7. 확인이 끝나면 검토상태를 검토 완료로 변경합니다.
여기까지 완료했다면

검토상태가 검토 완료로 표시되고 해당 지출 내역을 합계 계산에 사용할 수 있습니다.

11단계. 자동 실행 트리거 확인하기

  1. Apps Script 왼쪽의 알람시계 모양 트리거를 클릭합니다.
  2. 실행 함수가 processReceiptFiles인지 확인합니다.
  3. 이벤트 소스가 시간 기반인지 확인합니다.
  4. 15분 간격으로 설정되어 있는지 확인합니다.
여기까지 완료했다면

컴퓨터를 꺼도 구글 서버가 15분 간격으로 입력 폴더를 확인합니다.

무료 방법의 한계

1. 영수증 인식이 항상 정확하지는 않습니다

흐린 글자, 구겨진 종이, 빛 반사와 잘린 사진 때문에 날짜나 금액을 잘못 읽을 수 있습니다.

2. 파일 한 개에 영수증 한 건을 권장합니다

한 이미지나 PDF에 여러 영수증이 들어 있으면 날짜와 총액이 서로 섞일 수 있습니다.

3. 무료 코드에서는 파일을 12MB로 제한합니다

파일을 API에 직접 넣을 때 Base64 변환으로 데이터 크기가 증가합니다. 전체 요청을 20MB 미만으로 유지하기 위한 안전 제한입니다.

4. 한 번에 최대 5개 파일을 처리합니다

입력 폴더에 5개보다 많은 파일이 있으면 다음 자동 실행에서 남은 파일을 처리합니다.

5. 외화 환산은 하지 않습니다

해외 영수증은 USD, JPY 등 원래 통화로 저장합니다. 결제일 환율이나 카드사의 실제 청구 환율을 임의로 적용하지 않습니다.

6. 무료 티어의 데이터 처리 조건을 확인해야 합니다

Gemini API 무료 티어로 보낸 데이터는 Google 제품 개선에 사용될 수 있습니다. 민감한 개인정보는 사진에서 가린 뒤 업로드하는 것이 안전합니다.

7. generateContent API가 향후 변경될 수 있습니다

이 글의 코드는 현재 공식 문서에 남아 있는 generateContent API를 사용합니다.

Google은 최신 기능과 모델 사용에는 Interactions API를 권장하고 있으므로, 향후 API 정책이 바뀌면 코드 수정이 필요할 수 있습니다.

무료 방법에서 확인한 공식 범위
  • Gemini 3.5 Flash-Lite는 이미지와 PDF 입력 지원
  • 무료 티어 입력·출력 비용 없음
  • 인라인 이미지 요청 전체 크기 20MB 미만
  • Apps Script의 15분 시간 기반 트리거 사용 가능
  • Apps Script 개인 계정 URL Fetch 하루 20,000회
  • Apps Script 개인 계정 트리거 총 실행 시간 하루 90분

유료 방법: Make와 OpenAI로 만들기

유료 방법은 Apps Script 코드를 사용하지 않고 Make 화면에서 모듈을 연결합니다.

Google Drive에서 파일을 감지해 내려받은 뒤 Make AI Agent (New)가 OpenAI 모델로 파일을 분석하고 결과를 구글 시트에 저장합니다.

Make AI Agent (New)는 오픈 베타입니다.

아래 메뉴명은 2026년 7월 28일 공식 문서 기준입니다. 계정에 따라 화면 배치와 지원 모델 목록이 다를 수 있습니다.

유료 자동화의 흐름

Google Drive Download a File Make AI Agent Google Sheets 파일 이동
  • Make Core: 월간 결제 기준 10,000크레딧에 US$12
  • OpenAI 모델: GPT-5.6 Luna 권장
  • OpenAI 기본 가격: 입력 100만 토큰당 US$1, 출력 100만 토큰당 US$6
  • Make Core 파일 상한: 최대 100MB
  • 주의: OpenAI 및 Make AI Agent의 더 작은 제한이 우선 적용될 수 있음

1단계. Make용 폴더 만들기

  1. 구글 드라이브에서 Make_영수증_입력 폴더를 만듭니다.
  2. Make_영수증_처리완료 폴더도 만듭니다.
여기까지 완료했다면

두 개의 Make용 영수증 폴더가 표시됩니다.

2단계. 지출 내역 시트 만들기

새 스프레드시트 첫 번째 줄에 다음 항목을 입력합니다.

첫 번째 줄 항목
처리시간 | 구매일 | 상호명 | 총액 | 통화 | 세액 | 결제수단 | 카테고리 | 품목요약 | 인식신뢰도 | 검토상태 | 원본파일명 | 메모
여기까지 완료했다면

A1부터 M1까지 총 13개의 항목이 표시됩니다.

3단계. Make 시나리오 만들기

  1. Make에 로그인합니다.
  2. Scenarios를 클릭합니다.
  3. Create a new scenario를 클릭합니다.
  4. 큰 플러스 버튼을 클릭합니다.
  5. Google Drive를 검색합니다.
  6. Watch Files in a Folder를 선택합니다.
여기까지 완료했다면

시나리오 화면에 Google Drive 트리거가 표시됩니다.

4단계. 입력 폴더 연결하기

  1. Google Drive 모듈을 클릭합니다.
  2. Create a connection을 클릭합니다.
  3. 구글 계정을 연결합니다.
  4. Folder에서 Make_영수증_입력을 선택합니다.
  5. Limit은 처음에 3으로 설정합니다.
여기까지 완료했다면

첫 번째 모듈에 입력 폴더가 연결됩니다.

5단계. 지원 파일 필터 만들기

첫 번째 모듈 뒤에 필터를 추가하고 MIME type이 다음 중 하나일 때만 통과하도록 설정합니다.

허용 파일 형식
image/jpeg
image/png
application/pdf
여기까지 완료했다면

JPG, PNG, PDF 파일만 다음 모듈로 전달됩니다.

6단계. 파일 내려받기

  1. 필터 오른쪽에 Google Drive 모듈을 추가합니다.
  2. Download a File을 선택합니다.
  3. File ID에 첫 번째 모듈의 File ID를 연결합니다.
여기까지 완료했다면

파일명과 파일 데이터가 Download a File의 출력으로 만들어집니다.

7단계. OpenAI API 키 만들기

  1. OpenAI 개발자 플랫폼에 로그인합니다.
  2. API Keys 메뉴를 엽니다.
  3. Create new secret key를 클릭합니다.
  4. 키 이름을 Make Receipt Automation으로 입력합니다.
  5. 키를 복사합니다.
  6. Billing에서 결제 수단이나 선불 크레딧을 설정합니다.
ChatGPT Plus와 OpenAI API는 별도 결제입니다.

ChatGPT 구독료에 OpenAI API 사용료가 포함되는 것은 아닙니다.

8단계. Make AI Agent (New) 추가하기

  1. Download a File 오른쪽의 플러스 버튼을 클릭합니다.
  2. Make AI Agents (New)를 검색합니다.
  3. Run an Agent를 선택합니다.
  4. Connection에서 OpenAI 사용자 연결을 추가합니다.
  5. 앞에서 만든 OpenAI API 키를 입력합니다.
  6. Model에서 GPT-5.6 Luna를 선택합니다.

GPT-5.6 Luna가 목록에 보이지 않으면 Make에 표시되는 최신 이미지 입력 지원 모델을 선택합니다. Make의 모델 목록은 제공업체 업데이트 시점에 따라 다를 수 있습니다.

9단계. Instructions 입력하기

Instructions에는 에이전트가 항상 지켜야 할 역할과 규칙을 입력합니다.

Instructions 입력값
너는 영수증 지출 내역을 정확하게 추출하는 담당자다.

첨부된 파일에서 실제로 보이는 정보만 사용한다.
흐리거나 확인되지 않는 글자는 임의로 추측하지 않는다.

카드번호 전체, 승인번호, 회원번호, 전화번호는 출력하지 않는다.
외화는 영수증에 표시된 통화를 그대로 기록하며 원화로 환산하지 않는다.

카테고리는 다음 중 하나만 선택한다.
식비, 교통, 쇼핑, 업무, 의료, 교육, 숙박, 기타

신뢰도는 높음, 보통, 낮음 중 하나만 선택한다.
여기까지 완료했다면

Instructions 칸에 영수증 분석 기준이 저장됩니다.

10단계. Input과 Input files 연결하기

Input에는 이번 실행에서 처리할 구체적인 작업을 입력합니다.

Input 입력값
첨부된 영수증 파일을 분석하세요.

구매일, 상호명, 총액, 통화, 세액, 결제수단,
카테고리, 주요 품목, 인식 신뢰도와
사람이 확인해야 할 내용을 반환하세요.

확인할 수 없는 정보는 만들지 마세요.

Input files에는 다음과 같이 연결합니다.

  1. File name에 Download a File의 파일명을 연결합니다.
  2. Data에 Download a File의 파일 데이터를 연결합니다.
  3. Conversation ID는 비워둡니다.
Conversation ID를 비워두는 이유

각 영수증은 별도의 지출입니다. 비워두면 실행마다 새로운 대화가 만들어져 이전 영수증의 내용이 섞이는 것을 줄일 수 있습니다.

여기까지 완료했다면

파일 이름과 파일 데이터가 Input files에 연결됩니다.

11단계. Response format 설정하기

  1. Response format에서 Data structure를 선택합니다.
  2. Generate를 클릭합니다.
  3. 아래 예시 JSON을 붙여 넣습니다.
  4. 생성된 데이터 구조를 저장합니다.
데이터 구조 생성용 JSON
{
  "valid": true,
  "purchase_date": "2026-07-28",
  "merchant": "예시상점",
  "total_amount": 12000,
  "currency": "KRW",
  "tax_amount": 1091,
  "payment_method": "카드",
  "category": "식비",
  "items_summary": "음료, 샌드위치",
  "confidence": "높음",
  "notes": ""
}
여기까지 완료했다면

에이전트 출력에서 구매일, 상호명, 총액과 카테고리를 각각 선택할 수 있습니다.

12단계. 구글 시트에 행 추가하기

  1. AI Agent 오른쪽에 Google Sheets 모듈을 추가합니다.
  2. Add a Row를 선택합니다.
  3. 앞에서 만든 스프레드시트와 시트를 선택합니다.
  4. 각 열에 AI Agent의 해당 출력값을 연결합니다.
  5. 검토상태에는 검토 필요를 직접 입력합니다.
  6. 원본파일명에는 첫 번째 Google Drive 모듈의 파일명을 연결합니다.
여기까지 완료했다면

AI Agent 출력값이 구글 시트의 각 열에 연결됩니다.

13단계. 처리 완료 폴더로 이동하기

  1. Google Sheets 오른쪽에 Google Drive 모듈을 추가합니다.
  2. Move a File/Folder를 선택합니다.
  3. File ID에 첫 번째 Google Drive 모듈의 File ID를 연결합니다.
  4. 이동할 폴더로 Make_영수증_처리완료를 선택합니다.
여기까지 완료했다면

Google Drive, Download a File, Make AI Agent, Google Sheets, Move a File/Folder가 순서대로 연결됩니다.

14단계. 시험 실행하기

  1. Make 화면 아래쪽의 Run once를 클릭합니다.
  2. 그 이후 입력 폴더에 새 영수증을 업로드합니다.
  3. 각 모듈 위에 실행 숫자가 표시되는지 확인합니다.
  4. 구글 시트에 새 행이 추가됐는지 확인합니다.
  5. 파일이 처리 완료 폴더로 이동했는지 확인합니다.
정상 작동 결과

구글 시트에 구매일, 상호명, 총액, 통화와 카테고리가 추가됩니다.

15단계. 자동 실행 일정 설정하기

  1. 시나리오 도구 모음의 일정 설정을 엽니다.
  2. At regular intervals를 선택합니다.
  3. 15분 간격으로 설정합니다.
  4. 시간대가 Asia/Seoul인지 확인합니다.
  5. 저장한 뒤 시나리오를 활성화합니다.
실행 간격을 지나치게 짧게 설정하지 마세요.

폴더를 확인하는 트리거도 크레딧을 사용합니다. 개인 지출 정리라면 15분이나 1시간 간격으로도 충분합니다.

유료 방법의 예상 비용

  • Make Core: 월간 결제 기준 US$12, 월 10,000크레딧
  • GPT-5.6 Luna: 입력 100만 토큰당 US$1
  • GPT-5.6 Luna: 출력 100만 토큰당 US$6
  • Make Core 파일 처리 상한: 최대 100MB

영수증 한 장의 OpenAI 비용은 이미지 크기와 해상도, 분석 프롬프트와 출력 길이에 따라 달라집니다.

또한 영수증 한 건을 처리할 때 여러 Make 모듈이 실행되므로 영수증 한 건이 크레딧 한 개만 사용하는 구조가 아닙니다.

처음에는 시험 파일 5~10개만 처리하고 Make 실행 기록과 OpenAI 사용량 화면에서 실제 비용을 확인하세요.

무료와 유료 비교표

비교 항목 무료 방법 유료 방법
사용 도구 Google Drive, Apps Script, Gemini API Make Core, Make AI Agent, OpenAI API
초기 비용 0원 Make와 OpenAI 결제 설정 필요
월 예상 비용 무료 한도 내 0원 US$12와 API 사용량 비용
설정 난이도 중간, 코드 붙여 넣기 필요 중간, 모듈과 데이터 구조 설정 필요
예상 설정 시간 약 35~50분 약 30~45분
지원 파일 JPG, PNG, PDF JPG, PNG, PDF
파일 크기 제공 코드 기준 최대 12MB Make Core 최대 100MB, AI 모델 제한 추가 적용
자동 실행 15분마다 최대 5개 설정한 간격과 크레딧에 따라 실행
오류 확인 오류 폴더와 Apps Script 실행 기록 Make 모듈별 실행 기록
유지관리 코드와 API 변경 확인 필요 화면에서 연결과 출력 구조 수정
추천 대상 비용 없이 자동화를 배우려는 개인 코드 없이 여러 사람의 경비를 처리할 팀

정상 작동 확인 방법

무료 방법

  1. 드라이브에 입력·완료·오류 폴더가 생성됩니다.
  2. 지출내역 시트가 생성됩니다.
  3. 트리거 목록에 processReceiptFiles가 표시됩니다.
  4. 성공한 파일은 처리 완료 폴더로 이동합니다.
  5. 실패한 파일은 오류 폴더로 이동합니다.
  6. 시트에 구매일, 상호명과 총액이 기록됩니다.
  7. 원본보기 링크가 정상적으로 열립니다.

유료 방법

  1. 총 5개의 Make 모듈이 연결됩니다.
  2. Download a File에서 파일명과 Data가 출력됩니다.
  3. AI Agent의 Response format이 Data structure로 설정됩니다.
  4. 구글 시트에 새 행이 추가됩니다.
  5. 파일이 처리 완료 폴더로 이동합니다.
  6. 시나리오가 활성화되어 있습니다.

자주 발생하는 오류와 해결법

오류 1. Gemini API 키가 없다고 나옵니다

해결: 프로젝트 설정의 스크립트 속성에 GEMINI_API_KEY가 정확히 입력됐는지 확인합니다.

오류 2. 400 오류가 발생합니다

원인: 요청 형식이나 출력 스키마가 현재 API와 맞지 않거나 파일 데이터가 손상됐을 수 있습니다.

해결: 파일 크기와 형식을 확인하고, 코드가 중간에 빠지지 않았는지 다시 확인합니다.

오류 3. 401 또는 403 오류가 발생합니다

해결: Google AI Studio에서 새 API 키를 만들고 스크립트 속성의 값을 교체합니다.

오류 4. 파일이 오류 폴더로 이동합니다

지출내역 시트의 메모 열에서 원인을 확인합니다. 파일 크기, 형식, 사진 선명도를 점검한 뒤 입력 폴더에 다시 올립니다.

오류 5. 총액이 잘못 입력됩니다

공급가액, 세액과 총 결제금액이 함께 표시된 영수증에서 다른 숫자를 선택했을 수 있습니다. 원본을 확인해 총액을 직접 수정합니다.

오류 6. HEIC 사진이 처리되지 않습니다

HEIC 파일을 JPG로 변환하거나 사진 화면을 캡처해 PNG로 만든 뒤 다시 올립니다.

오류 7. PDF가 처리되지 않습니다

암호화된 PDF인지 확인하고, 영수증 한 페이지만 남긴 작은 PDF로 다시 저장합니다.

오류 8. Make가 기존 파일을 감지하지 않습니다

Run once를 먼저 누른 뒤 새로운 파일을 업로드합니다. 기존 파일은 복사본을 만들어 새 파일로 시험합니다.

오류 9. Make AI Agent가 파일을 읽지 못합니다

  1. Download a File 모듈이 성공했는지 확인합니다.
  2. Input files의 File name에 파일명을 연결합니다.
  3. Input files의 Data에 Download a File의 Data를 연결합니다.
  4. 선택한 OpenAI 모델이 이미지 또는 PDF 입력을 지원하는지 확인합니다.

오류 10. AI 출력값을 구글 시트에서 선택할 수 없습니다

Response format이 Text로 되어 있을 가능성이 있습니다. Data structure를 선택하고 예시 JSON으로 구조를 다시 생성합니다.

오류 11. Make 파일 크기 오류가 발생합니다

Core 자체 상한은 100MB이지만 AI Agent 또는 OpenAI 모델의 더 작은 제한이 먼저 적용될 수 있습니다. 사진을 잘라내거나 압축해 다시 시험합니다.

오류 12. 같은 영수증이 두 번 입력됩니다

처리 완료 파일을 입력 폴더에 다시 넣지 않습니다. 구매일·상호명·총액이 같은 중복 행은 직접 삭제합니다.

오류 13. Make 크레딧이 빠르게 줄어듭니다

실행 간격을 15분 또는 1시간으로 늘리고, 시험이 끝난 시나리오는 비활성화합니다.

개인정보 및 보안 주의사항

  • 카드번호, 승인번호와 회원번호는 가능한 한 가립니다.
  • 이름, 주소와 전화번호가 필요하지 않다면 사진에서 잘라냅니다.
  • API 키가 보이는 화면을 캡처하지 않습니다.
  • 구글 드라이브 폴더를 공개로 공유하지 않습니다.
  • Make 실행 기록에도 파일명과 분석 결과가 남을 수 있습니다.
  • 세금 신고용 원본 영수증은 별도로 보관합니다.
  • AI가 읽은 결과만으로 비용 인정 여부를 판단하지 않습니다.
  • 퇴사자와 외부 사용자의 폴더 접근 권한을 삭제합니다.
  • 사용하지 않는 API 키와 트리거를 삭제합니다.
세무·회계 증빙의 최종 판단은 사람이 해야 합니다.

AI 자동화는 반복 입력을 줄이는 보조 도구입니다. 비용 인정 여부와 증빙 보관 기준은 공식 기관 안내 또는 전문가를 통해 확인하세요.

어떤 사람에게 어떤 방법이 적합할까?

무료 방법 추천 대상

  • 개인 가계부를 구글 시트로 관리하는 사람
  • 영수증 수가 많지 않은 소규모 사업자
  • 비용 없이 자동화를 배우고 싶은 사람
  • 분석 결과를 직접 검토할 수 있는 사람

유료 방법 추천 대상

  • 코드를 직접 관리하고 싶지 않은 사람
  • 여러 팀원이 영수증을 업로드하는 회사
  • 실행 오류를 화면에서 확인하고 싶은 사람
  • Slack, Notion 또는 다른 업무 시스템으로 확장할 사람

FAQ

Q1. 컴퓨터를 계속 켜 둬야 하나요?

아닙니다. Apps Script와 Make 모두 클라우드에서 실행됩니다.

Q2. 여러 영수증을 한 번에 올려도 되나요?

가능합니다. 무료 코드는 한 번 실행할 때 최대 5개를 처리합니다.

Q3. 외국 영수증도 처리할 수 있나요?

가능합니다. 영수증에 표시된 통화 그대로 기록하며 원화로 환산하지 않습니다.

Q4. 카드번호도 저장되나요?

출력하지 않도록 지시했지만, 민감한 부분은 사진에서 직접 가리는 것이 더 안전합니다.

Q5. Make 무료 요금제로도 가능한가요?

Make의 자체 AI 제공자는 무료 플랜에서도 사용할 수 있지만, 개인 OpenAI API 연결을 사용하는 방법은 유료 Make 요금제가 필요합니다.

Q6. ChatGPT Plus를 사용하면 API가 무료인가요?

아닙니다. ChatGPT 구독과 OpenAI API는 별도입니다.

Q7. 월별 지출 합계도 만들 수 있나요?

구글 시트의 피벗 테이블이나 QUERY 함수를 사용해 월별·카테고리별 합계를 만들 수 있습니다.

Q8. 세금 신고에 바로 사용할 수 있나요?

아닙니다. AI 결과를 원본 증빙과 비교하고 관련 세무 기준을 별도로 확인해야 합니다.

Q9. 오류 폴더의 파일은 어떻게 다시 처리하나요?

지출내역 시트의 메모를 확인하고 파일을 수정한 뒤 입력 폴더로 다시 이동합니다.

Q10. API가 변경되면 어떻게 하나요?

Google이 generateContent 지원 방식을 변경하면 코드의 API 주소와 요청 구조를 수정해야 할 수 있습니다.

마무리

이번 글에서는 영수증 사진이나 PDF를 구글 드라이브에 넣으면 AI가 구매일, 상호명, 총액과 지출 카테고리를 읽어 구글 스프레드시트에 저장하는 자동화를 만들었습니다.

무료 방법은 Apps Script와 Gemini API를 사용하므로 별도의 월 이용료 없이 자동화의 전체 흐름을 배울 수 있습니다.

코드를 직접 관리하기 어렵거나 여러 팀원이 경비 자료를 올린다면 Make Core와 OpenAI를 연결하는 방법이 더 편리합니다.

처음에는 개인정보가 적은 시험용 영수증으로 확인하세요. 구매일과 총액이 정확하게 입력되는 것을 확인한 뒤 실제 경비 정리에 적용하는 것이 안전합니다.

핵심 한 줄 정리

영수증 사진과 AI를 연결하면 반복적인 지출 입력 시간을 줄일 수 있지만, 최종 금액 검토는 반드시 사람이 해야 합니다.

관련글 더보기