데이터가 시트 안에만 있으면 GOOGLEFINANCE가 가장 간단합니다. 애플리케이션에서 API를 호출해야 할 때 Apps Script를 대안으로 사용하세요.
- GOOGLEFINANCE로 현재값과 날짜 범위를 가져옵니다.
- GOOGLEFINANCE의 과거 데이터는 Sheets API나 Apps Script로 읽을 수 없습니다.
- FXRATE 사용자 함수는 JSON을 호출하고 캐시할 수 있습니다.
- 공유 시트의 Apps Script에 API 키를 넣지 마세요.
- 값을 표시할 때 source와 관측 시각을 보존하세요.
시트에 키를 넣지 않고 시작하기
최신 환율 엔드포인트는 익명 요청을 허용하며 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 같은 최신성 필드가 포함됩니다. 아래 값은 응답 형태를 보여주는 예시이며, 실제 호출에서는 현재 값과 시각이 반환됩니다. 숫자의 관측 시점을 설명해야 한다면 메타데이터도 함께 보관하세요.
Google의 사용자 지정 함수 안내는 URL Fetch를 지원되는 서비스로 설명합니다. 요청이 실패하면 Apps Script의 실행 화면에서 응답과 오류 세부 정보를 확인할 수 있습니다.
새로 고침과 무료 한도 이해하기
사용자 지정 함수는 입력이 바뀌었다고 Sheets가 판단할 때 다시 계산되며 실시간 스트리밍 피드는 아닙니다. 시간 기반 트리거가 모든 수식 셀을 자동으로 새로 고치지도 않습니다. 예약 갱신이 필요한 대시보드는 별도 트리거 함수가 필요한 통화쌍을 가져와 범위에 쓰도록 하세요. 일반적인 모델은 시트를 다시 계산하거나 입력을 수정하면 캐시되지 않은 값을 요청할 수 있습니다.
- 익명 접근: IP당 분당 10회, 시간당 100회
CacheService는 보통 10분간 같은 환율을 재사용하지만 Google이 일찍 삭제할 수 있음- Google이 여러 사용자를 공유 출구로 보낼 수 있어 실제 익명 처리량은 달라질 수 있음
- 공유 시트에서 계정에 귀속된 한도가 필요하면 키를 보관하는 서버 측 프록시 사용
| 코드 | 증상 | 해결 |
|---|---|---|
| 401 또는 403 | 셀에 #ACCESS_ERROR 표시 | Apps Script → 실행을 확인하세요. 공유 바인드 스크립트에 키를 붙여 넣지 말고 서버 측 프록시를 사용하세요. |
| 429 | 셀에 #RATE_LIMIT 표시 | IP별 한도에 도달했습니다. CACHE_TTL을 높이거나 서로 다른 통화쌍 수를 줄이고 키가 있는 프록시로 옮기세요. |
| 200이지만 환율 없음 | 셀에 #NO_PAIR 표시 | 응답에 해당 통화가 없습니다. ISO 코드를 확인하세요(예: JYP가 아니라 JPY). |
| 기타 | 셀에 #ERROR 표시 | Apps Script → 실행에서 상위 응답 상태나 런타임 오류를 확인한 뒤 다시 시도하세요. |
바인드된 Apps Script 프로젝트는 모든 편집자가 볼 수 있습니다. 이 예제는 익명 접근으로 자격 증명을 시트에 두지 않습니다. 공유 통합 문서에 인증 처리량이 필요하면 Bearer 토큰을 서버 측 서비스에 보관하고 시트는 그 서비스만 호출하게 하세요. 반환되는 환율은 여전히 인디케이티브 값이며 결제 정산용이 아닙니다.
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.gsconst 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")