
안녕하세요 IT 세상입니다.
이번 글에서는 영수증 사진이나 PDF 파일을 구글 드라이브 폴더에 넣으면 AI가 구매 날짜, 상호명, 총금액, 결제수단과 지출 카테고리를 읽어 구글 스프레드시트에 자동으로 정리하는 방법을 알아보겠습니다.
무료 방법은 Google Apps Script와 Gemini API 무료 티어를 사용합니다. 코드를 직접 관리하기 어렵다면 Make와 OpenAI를 연결하는 유료 방법도 함께 따라 할 수 있습니다.

사진이 흐리거나 빛이 반사되면 AI가 숫자를 잘못 읽을 수 있습니다. 총액, 구매일, 통화는 반드시 원본 영수증과 비교해야 합니다.
화면의 메뉴 이름, 지원 모델과 크레딧 계산 방식은 업데이트에 따라 달라질 수 있습니다. 실제 설정 화면에 표시되는 항목과 비용을 다시 확인하세요.
OCR은 사진 속 글자를 컴퓨터가 읽을 수 있는 데이터로 바꾸는 기술입니다.
이번 자동화에서는 별도의 OCR 프로그램을 추가하지 않고, 이미지와 PDF를 이해할 수 있는 AI 모델이 영수증 내용을 직접 분석합니다.
| 저장 항목 | 내용 |
|---|---|
| 구매일 | 영수증에 표시된 실제 결제 날짜 |
| 상호명 | 가게·병원·호텔·판매처 등의 이름 |
| 총액 | 최종적으로 결제한 금액 |
| 통화 | KRW, USD, JPY 등의 통화 코드 |
| 세액 | 부가세나 세금이 따로 표시된 경우 해당 금액 |
| 결제수단 | 카드, 현금, 간편결제 또는 확인 필요 |
| 카테고리 | 식비, 교통, 쇼핑, 업무, 의료, 교육, 숙박, 기타 |
| 품목 요약 | 영수증에서 확인되는 주요 구매 품목 |
| 인식 신뢰도 | 높음, 보통, 낮음 |
| 검토 상태 | 검토 필요, 검토 완료, 수정 필요 |
무료 방법에서는 Apps Script가 구글 드라이브의 입력 폴더를 15분마다 확인합니다.
새로운 JPG, PNG 또는 PDF가 발견되면 Gemini가 파일을 분석하고 결과를 구글 스프레드시트에 기록합니다.
스프레드시트 왼쪽 위에 ‘AI 영수증 지출 관리’라는 이름이 표시됩니다.
Apps Script 편집기에 비어 있는 코드 입력창이 표시됩니다.
아래 코드를 처음부터 끝까지 복사해 Apps Script 편집기에 붙여 넣습니다.
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
);
}
});
}
코드를 붙여 넣은 뒤 위쪽의 디스크 모양 저장 버튼을 클릭합니다.
코드가 저장되고 편집기 왼쪽에 빨간 오류 표시가 나타나지 않습니다.
API 키를 블로그 코드, 캡처 이미지 또는 공개 문서에 넣지 마세요.
Google AI Studio에 복사할 수 있는 API 키가 표시됩니다.
스크립트 속성 목록에 GEMINI_API_KEY가 표시됩니다.
구글 드라이브에 AI_영수증_입력, AI_영수증_처리완료, AI_영수증_오류 폴더가 생성됩니다.
스프레드시트에는 지출내역 시트가 생성됩니다.
이번 무료 코드는 JPG, PNG와 PDF만 처리합니다. HEIC 사진은 JPG로 변환하거나 화면을 캡처한 뒤 사용하세요.
입력 폴더에 JPG, PNG 또는 PDF 파일이 표시됩니다.
구매일, 상호명, 총액, 통화, 결제수단과 카테고리가 두 번째 줄에 표시됩니다.
성공한 원본 파일은 AI_영수증_처리완료 폴더로 이동합니다.
검토상태가 검토 완료로 표시되고 해당 지출 내역을 합계 계산에 사용할 수 있습니다.
컴퓨터를 꺼도 구글 서버가 15분 간격으로 입력 폴더를 확인합니다.
흐린 글자, 구겨진 종이, 빛 반사와 잘린 사진 때문에 날짜나 금액을 잘못 읽을 수 있습니다.
한 이미지나 PDF에 여러 영수증이 들어 있으면 날짜와 총액이 서로 섞일 수 있습니다.
파일을 API에 직접 넣을 때 Base64 변환으로 데이터 크기가 증가합니다. 전체 요청을 20MB 미만으로 유지하기 위한 안전 제한입니다.
입력 폴더에 5개보다 많은 파일이 있으면 다음 자동 실행에서 남은 파일을 처리합니다.
해외 영수증은 USD, JPY 등 원래 통화로 저장합니다. 결제일 환율이나 카드사의 실제 청구 환율을 임의로 적용하지 않습니다.
Gemini API 무료 티어로 보낸 데이터는 Google 제품 개선에 사용될 수 있습니다. 민감한 개인정보는 사진에서 가린 뒤 업로드하는 것이 안전합니다.
이 글의 코드는 현재 공식 문서에 남아 있는 generateContent API를 사용합니다.
Google은 최신 기능과 모델 사용에는 Interactions API를 권장하고 있으므로, 향후 API 정책이 바뀌면 코드 수정이 필요할 수 있습니다.
유료 방법은 Apps Script 코드를 사용하지 않고 Make 화면에서 모듈을 연결합니다.
Google Drive에서 파일을 감지해 내려받은 뒤 Make AI Agent (New)가 OpenAI 모델로 파일을 분석하고 결과를 구글 시트에 저장합니다.
아래 메뉴명은 2026년 7월 28일 공식 문서 기준입니다. 계정에 따라 화면 배치와 지원 모델 목록이 다를 수 있습니다.
두 개의 Make용 영수증 폴더가 표시됩니다.
새 스프레드시트 첫 번째 줄에 다음 항목을 입력합니다.
처리시간 | 구매일 | 상호명 | 총액 | 통화 | 세액 | 결제수단 | 카테고리 | 품목요약 | 인식신뢰도 | 검토상태 | 원본파일명 | 메모
A1부터 M1까지 총 13개의 항목이 표시됩니다.
시나리오 화면에 Google Drive 트리거가 표시됩니다.
첫 번째 모듈에 입력 폴더가 연결됩니다.
첫 번째 모듈 뒤에 필터를 추가하고 MIME type이 다음 중 하나일 때만 통과하도록 설정합니다.
image/jpeg
image/png
application/pdf
JPG, PNG, PDF 파일만 다음 모듈로 전달됩니다.
파일명과 파일 데이터가 Download a File의 출력으로 만들어집니다.
ChatGPT 구독료에 OpenAI API 사용료가 포함되는 것은 아닙니다.
GPT-5.6 Luna가 목록에 보이지 않으면 Make에 표시되는 최신 이미지 입력 지원 모델을 선택합니다. Make의 모델 목록은 제공업체 업데이트 시점에 따라 다를 수 있습니다.
Instructions에는 에이전트가 항상 지켜야 할 역할과 규칙을 입력합니다.
너는 영수증 지출 내역을 정확하게 추출하는 담당자다.
첨부된 파일에서 실제로 보이는 정보만 사용한다.
흐리거나 확인되지 않는 글자는 임의로 추측하지 않는다.
카드번호 전체, 승인번호, 회원번호, 전화번호는 출력하지 않는다.
외화는 영수증에 표시된 통화를 그대로 기록하며 원화로 환산하지 않는다.
카테고리는 다음 중 하나만 선택한다.
식비, 교통, 쇼핑, 업무, 의료, 교육, 숙박, 기타
신뢰도는 높음, 보통, 낮음 중 하나만 선택한다.
Instructions 칸에 영수증 분석 기준이 저장됩니다.
Input에는 이번 실행에서 처리할 구체적인 작업을 입력합니다.
첨부된 영수증 파일을 분석하세요.
구매일, 상호명, 총액, 통화, 세액, 결제수단,
카테고리, 주요 품목, 인식 신뢰도와
사람이 확인해야 할 내용을 반환하세요.
확인할 수 없는 정보는 만들지 마세요.
Input files에는 다음과 같이 연결합니다.
각 영수증은 별도의 지출입니다. 비워두면 실행마다 새로운 대화가 만들어져 이전 영수증의 내용이 섞이는 것을 줄일 수 있습니다.
파일 이름과 파일 데이터가 Input files에 연결됩니다.
{
"valid": true,
"purchase_date": "2026-07-28",
"merchant": "예시상점",
"total_amount": 12000,
"currency": "KRW",
"tax_amount": 1091,
"payment_method": "카드",
"category": "식비",
"items_summary": "음료, 샌드위치",
"confidence": "높음",
"notes": ""
}
에이전트 출력에서 구매일, 상호명, 총액과 카테고리를 각각 선택할 수 있습니다.
AI Agent 출력값이 구글 시트의 각 열에 연결됩니다.
Google Drive, Download a File, Make AI Agent, Google Sheets, Move a File/Folder가 순서대로 연결됩니다.
구글 시트에 구매일, 상호명, 총액, 통화와 카테고리가 추가됩니다.
폴더를 확인하는 트리거도 크레딧을 사용합니다. 개인 지출 정리라면 15분이나 1시간 간격으로도 충분합니다.
영수증 한 장의 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 변경 확인 필요 | 화면에서 연결과 출력 구조 수정 |
| 추천 대상 | 비용 없이 자동화를 배우려는 개인 | 코드 없이 여러 사람의 경비를 처리할 팀 |
해결: 프로젝트 설정의 스크립트 속성에 GEMINI_API_KEY가 정확히 입력됐는지 확인합니다.
원인: 요청 형식이나 출력 스키마가 현재 API와 맞지 않거나 파일 데이터가 손상됐을 수 있습니다.
해결: 파일 크기와 형식을 확인하고, 코드가 중간에 빠지지 않았는지 다시 확인합니다.
해결: Google AI Studio에서 새 API 키를 만들고 스크립트 속성의 값을 교체합니다.
지출내역 시트의 메모 열에서 원인을 확인합니다. 파일 크기, 형식, 사진 선명도를 점검한 뒤 입력 폴더에 다시 올립니다.
공급가액, 세액과 총 결제금액이 함께 표시된 영수증에서 다른 숫자를 선택했을 수 있습니다. 원본을 확인해 총액을 직접 수정합니다.
HEIC 파일을 JPG로 변환하거나 사진 화면을 캡처해 PNG로 만든 뒤 다시 올립니다.
암호화된 PDF인지 확인하고, 영수증 한 페이지만 남긴 작은 PDF로 다시 저장합니다.
Run once를 먼저 누른 뒤 새로운 파일을 업로드합니다. 기존 파일은 복사본을 만들어 새 파일로 시험합니다.
Response format이 Text로 되어 있을 가능성이 있습니다. Data structure를 선택하고 예시 JSON으로 구조를 다시 생성합니다.
Core 자체 상한은 100MB이지만 AI Agent 또는 OpenAI 모델의 더 작은 제한이 먼저 적용될 수 있습니다. 사진을 잘라내거나 압축해 다시 시험합니다.
처리 완료 파일을 입력 폴더에 다시 넣지 않습니다. 구매일·상호명·총액이 같은 중복 행은 직접 삭제합니다.
실행 간격을 15분 또는 1시간으로 늘리고, 시험이 끝난 시나리오는 비활성화합니다.
AI 자동화는 반복 입력을 줄이는 보조 도구입니다. 비용 인정 여부와 증빙 보관 기준은 공식 기관 안내 또는 전문가를 통해 확인하세요.
아닙니다. Apps Script와 Make 모두 클라우드에서 실행됩니다.
가능합니다. 무료 코드는 한 번 실행할 때 최대 5개를 처리합니다.
가능합니다. 영수증에 표시된 통화 그대로 기록하며 원화로 환산하지 않습니다.
출력하지 않도록 지시했지만, 민감한 부분은 사진에서 직접 가리는 것이 더 안전합니다.
Make의 자체 AI 제공자는 무료 플랜에서도 사용할 수 있지만, 개인 OpenAI API 연결을 사용하는 방법은 유료 Make 요금제가 필요합니다.
아닙니다. ChatGPT 구독과 OpenAI API는 별도입니다.
구글 시트의 피벗 테이블이나 QUERY 함수를 사용해 월별·카테고리별 합계를 만들 수 있습니다.
아닙니다. AI 결과를 원본 증빙과 비교하고 관련 세무 기준을 별도로 확인해야 합니다.
지출내역 시트의 메모를 확인하고 파일을 수정한 뒤 입력 폴더로 다시 이동합니다.
Google이 generateContent 지원 방식을 변경하면 코드의 API 주소와 요청 구조를 수정해야 할 수 있습니다.
이번 글에서는 영수증 사진이나 PDF를 구글 드라이브에 넣으면 AI가 구매일, 상호명, 총액과 지출 카테고리를 읽어 구글 스프레드시트에 저장하는 자동화를 만들었습니다.
무료 방법은 Apps Script와 Gemini API를 사용하므로 별도의 월 이용료 없이 자동화의 전체 흐름을 배울 수 있습니다.
코드를 직접 관리하기 어렵거나 여러 팀원이 경비 자료를 올린다면 Make Core와 OpenAI를 연결하는 방법이 더 편리합니다.
처음에는 개인정보가 적은 시험용 영수증으로 확인하세요. 구매일과 총액이 정확하게 입력되는 것을 확인한 뒤 실제 경비 정리에 적용하는 것이 안전합니다.
영수증 사진과 AI를 연결하면 반복적인 지출 입력 시간을 줄일 수 있지만, 최종 금액 검토는 반드시 사람이 해야 합니다.
| 구글 시트 재고 부족 알림 자동화, 소규모 사업자도 무료로 만들기 (2026 최신판) (0) | 2026.07.30 |
|---|---|
| Make란? 코딩 없이 무료로 시작하는 초보자 업무 자동화 도구 (2026 최신판) (0) | 2026.07.29 |
| 🚀 "[AI 개발 7단계] 생성형 AI(Generative AI) 기반 가이드: 영화, 이미지, 음악을 즐기는 인공지능" (6) | 2025.02.24 |
| 🚀 "[AI 개발 6단계] 강화 학습(Reinforcement Learning) 기초 가이드: AI가 스스로 학습하는 방법" (4) | 2025.02.23 |
| 🚀 "[AI 개발 5단계] 전이 학습(Transfer Learning) 가이드: 사전 학습된 모델로 AI 성능 극대화" (4) | 2025.02.23 |