데이터가 시트 안에만 있으면 GOOGLEFINANCE가 가장 간단합니다. 애플리케이션에서 API를 호출해야 할 때 Apps Script를 대안으로 사용하세요.
시트에 키를 넣지 않고 시작하기
최신 환율 엔드포인트는 익명 요청을 허용하며 IP당 분당 10회, 시간당 100회로 제한됩니다. 개인용 시트 예제에는 이 방식을 사용합니다. 바인드된 Apps Script 프로젝트는 모든 스프레드시트 편집자가 볼 수 있으므로 Bearer 토큰을 두기에 안전하지 않습니다. 공유 시트나 제품이라면 Google Sheets 밖에 키를 보관하는 서버 측 프록시를 통해 요청하세요.
시트에서 Apps Script 열기
환율을 넣을 Google Sheets를 엽니다. 확장 프로그램 → Apps Script를 선택합니다. 기본으로 들어 있는 function myFunction() {}를 삭제하고 아래 코드를 붙여 넣으세요. 이 스크립트는 시트에 바인드되므로 애드온 없이 함수를 사용할 수 있지만 다른 편집자도 코드를 볼 수 있습니다.
FXRATE 함수 붙여 넣기
아래 함수는 Authorization 헤더 없이 exchangerate.dev를 호출하고 각 통화쌍을 10분간 캐시합니다. 접근 권한, 한도, 통화쌍 검증에 실패하면 셀에 읽을 수 있는 오류 표시를 반환합니다. 같은 통화쌍을 요청하는 셀은 보통 하나의 캐시 응답을 공유합니다. CacheService는 최선의 노력 방식이므로 요청한 만료 전에도 항목이 사라질 수 있습니다.
어느 셀에서나 =FXRATE() 사용하기
시트로 돌아가 빈 셀에 =FXRATE("USD","KRW")를 입력하세요. 캐시되지 않은 첫 호출은 Apps Script 실행과 외부 요청 때문에 몇 초가 걸릴 수 있습니다. 지원되는 통화쌍이면 두 통화 코드를 바꿔 사용할 수 있습니다. 원화 예제는 =FXRATE("USD","KRW")(1달러당 원), =FXRATE("EUR","KRW")(1유로당 원), =100*FXRATE("JPY","KRW")(100엔당 원)입니다. 반대로 =FXRATE("KRW","JPY")는 1원당 엔화입니다. 아래는 함수를 다른 통화에도 적용하는 공통 예제이며 주석의 숫자는 현재 시세가 아닙니다:
API 응답에는 기준 통화, 요청한 통화쌍, 타임스탬프와 source, market_session 같은 최신성 필드가 포함됩니다. 아래 값은 응답 형태를 보여주는 예시이며, 실제 호출에서는 현재 값과 시각이 반환됩니다. 숫자의 관측 시점을 설명해야 한다면 메타데이터도 함께 보관하세요.
새로 고침과 무료 한도 이해하기
사용자 지정 함수는 입력이 바뀌었다고 Sheets가 판단할 때 다시 계산되며 실시간 스트리밍 피드는 아닙니다. 시간 기반 트리거가 모든 수식 셀을 자동으로 새로 고치지도 않습니다. 예약 갱신이 필요한 대시보드는 별도 트리거 함수가 필요한 통화쌍을 가져와 범위에 쓰도록 하세요. 일반적인 모델은 시트를 다시 계산하거나 입력을 수정하면 캐시되지 않은 값을 요청할 수 있습니다.
- 익명 접근: IP당 분당 10회, 시간당 100회
CacheService는 보통 10분간 같은 환율을 재사용하지만 Google이 일찍 삭제할 수 있음- Google이 여러 사용자를 공유 출구로 보낼 수 있어 실제 익명 처리량은 달라질 수 있음
- 공유 시트에서 계정에 귀속된 한도가 필요하면 키를 보관하는 서버 측 프록시 사용
GOOGLEFINANCE로 시작하기
현재값에는 =GOOGLEFINANCE("CURRENCY:USDEUR"), 일별 과거값에는 시작일과 종료일, DAILY를 사용합니다. 가격에는 지연이 있을 수 있고 과거 데이터는 Sheets API나 Apps Script에서 읽을 수 없습니다.
=GOOGLEFINANCE("CURRENCY:USDEUR")
=GOOGLEFINANCE("CURRENCY:USDEUR", "price", DATE(2026, 1, 1), DATE(2026, 1, 31), "DAILY")// google-sheets: Apps Script Code.gs
const CACHE_TTL = 600; // Cache successful responses for 10 minutes.
/**
* Returns an indicative exchange rate for one currency pair.
* @param {string} from Three-letter base currency code.
* @param {string} to Three-letter quote currency code.
* @return {number|string} The rate or a readable error marker.
* @customfunction
*/
function FXRATE(from, to) {
from = (from || 'USD').toString().trim().toUpperCase();
to = (to || 'EUR').toString().trim().toUpperCase();
if (!/^[A-Z]{3}$/.test(from) || !/^[A-Z]{3}$/.test(to)) return '#NO_PAIR';
const cacheKey = `fx_${from}_${to}`;
const cache = CacheService.getScriptCache();
const cached = cache.get(cacheKey);
if (cached) return Number(cached);
const url = `https://api.exchangerate.dev/v1/latest/${from}?symbols=${to}`;
let resp;
try {
resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
} catch {
return '#FETCH_ERROR';
}
const code = resp.getResponseCode();
if (code === 401 || code === 403) return '#ACCESS_ERROR';
if (code === 429) return '#RATE_LIMIT';
if (code !== 200) return '#ERROR';
let data;
try { data = JSON.parse(resp.getContentText()); }
catch { return '#INVALID_RESPONSE'; }
const rate = data && data.rates && data.rates[to];
if (rate == null) return '#NO_PAIR';
if (typeof rate !== 'number' || !Number.isFinite(rate) || rate <= 0) return '#INVALID_RESPONSE';
cache.put(cacheKey, String(rate), CACHE_TTL);
return Number(rate); // coerce so cells sum and sort
}// Euro per US dollar
=FXRATE("USD", "EUR") // 0.9245
// Japanese yen per US dollar
=FXRATE("USD", "JPY") // 157.32
// British pound per euro
=FXRATE("EUR", "GBP") // 0.8531
// Indonesian rupiah per US dollar
=FXRATE("USD", "IDR") // 16285.0{
"result": "success",
"base": "USD",
"source": "live",
"market_session": "open",
"timestamp": "2026-06-29T09:14:02Z",
"data_updated_at": "2026-06-29T09:14:00Z",
"rates": { "JPY": 157.32 },
"sources": { "JPY": "live" },
"notice": "Indicative rates, not for settlement."
}=FXRATE("USD", "EUR")