응답 전체를 하나의 관측값으로 다루기 메타데이터가 없는 환율은 의미가 불분명하다. 장중 현물, 일일 기준값, 다른 통화를 거쳐 계산한 교차환율일 수 있다. rates.EUR만 꺼내지 말고 객체 전체를 저장한다. API 레퍼런스 는 응답 필드를 정의하고, 방법론 은 출처와 시장 상태 라벨이 정해지는 방식을 설명한다.
json · abbreviated latest response copy
{
"base": "USD",
"source": "live",
"market_session": "open",
"timestamp": "2026-08-23T09:42:06Z",
"data_updated_at": "2026-08-23T09:41:00Z",
"rates": { "EUR": 0.86124, "GBP": 0.74281 },
"sources": { "EUR": "live", "GBP": "live" },
"derived_symbols": []
} source는 여러 통화 응답에서 가장 덜 최신인 데이터 등급을 요약한다. 통화별 상태는 sources를 읽고, derived_symbols로 중간 통화를 거쳐 계산된 환율을 확인한다.
두 시각은 의미가 다르다
timestamp는 API가 응답을 만든 시각이고 data_updated_at은 원본 관측값이 기록된 시각이다. 최신 여부와 화면 표시는 후자를 기준으로 한다.
타이머보다 먼저 갱신 정책 정하기 모든 제품에 맞는 간격은 없다. 쇼핑몰의 대략적 표시값은 몇 분간 공유 캐시할 수 있고, 운영 대시보드는 보이는 동안 짧게 갱신할 수 있으며, 일일 보고서는 필요한 고시 이후 한 번 가져올 수 있다. 렌더링마다 요청해도 같은 관측값이 돌아올 수 있다.
용도 앱 정책 표시할 내용
쇼핑몰 예상가 공유 캐시, 몇 분마다 갱신 통화, 참고용 표시, 관측 시각
운영 대시보드 짧은 캐시, 보이는 동안 갱신 환율, 출처, 시장 상태, 시각
일일 보고서 필요한 고시 이후 한 번 환율과 저장된 관측 시각
결제 금액 참고 환율을 사용하지 않음 결제 또는 거래 제공자 결과
market_session은 open, weekend, interbank_closed 중 하나다. 활발한 통화는 거래 주간에 움직이고 일일 기준 시계열은 발표 일정에 따라 갱신된다.
서버에 작은 캐시 만들기 통화 코드를 정렬해 안정적인 키를 만들고 성공한 응답만 저장한다. 동시에 들어온 갱신은 하나로 합치며 인증 요청과 API 키는 서버에 둔다.
typescript · latest-rates.ts copy
type RateSource = "live" | "ecb_daily" | "fred_daily";
type LatestRates = {
base: string;
source: RateSource;
sources: Record<string, RateSource>;
market_session: "open" | "weekend" | "interbank_closed";
timestamp: string;
data_updated_at: string;
rates: Record<string, number>;
derived_symbols: string[];
};
type CacheEntry = {
value: LatestRates;
fetchedAt: number;
};
const cache = new Map<string, CacheEntry>();
const pending = new Map<string, Promise<LatestRates>>();
function cacheKey(base: string, symbols: string[]): string {
return `${base}:${[...symbols].sort().join(",")}`;
}
async function fetchLatest(base: string, symbols: string[]): Promise<LatestRates> {
const url = new URL(`https://api.exchangerate.dev/v1/latest/${base}`);
url.searchParams.set("symbols", [...symbols].sort().join(","));
const response = await fetch(url, {
headers: process.env.EXCHANGERATE_API_KEY
? { Authorization: `Bearer ${process.env.EXCHANGERATE_API_KEY}` }
: {},
signal: AbortSignal.timeout(8000),
});
if (!response.ok) {
throw new Error(`FX refresh failed: ${response.status}`);
}
return response.json() as Promise<LatestRates>;
}
export async function getLatestRates(
base: string,
symbols: string[],
maxAgeMs = 60_000,
): Promise<LatestRates> {
const key = cacheKey(base, symbols);
const existing = cache.get(key);
if (existing && Date.now() - existing.fetchedAt < maxAgeMs) {
return existing.value;
}
const inFlight = pending.get(key);
if (inFlight) return inFlight;
const refresh = fetchLatest(base, symbols)
.then((value) => {
cache.set(key, { value, fetchedAt: Date.now() });
return value;
})
.finally(() => pending.delete(key));
pending.set(key, refresh);
return refresh;
} 인스턴스가 여러 개라면 공유 저장소나 프레임워크 캐시를 사용한다. 값과 관측 메타데이터를 함께 저장하고 성공 시에만 교체한다는 원칙은 같다.
갱신 실패는 새 환율이 아니다 타임아웃, 429, 상류 오류는 시장 관측값이 아니다. 마지막 정상값을 허용하는 기능이라면 원래 data_updated_at과 대체 상태를 표시한다. 정상값이 없으면 오류를 반환한다. 429에서는 Retry-After를 따르고 일시 오류에는 무작위 지연을 포함한 지수 백오프를 쓴다. 401은 설정을 고쳐야 한다.
typescript · explicit stale fallback copy
type ConverterResult =
| { status: "fresh"; data: LatestRates }
| { status: "last_known_good"; data: LatestRates; reason: string };
async function getConverterRates(
base: string,
symbols: string[],
): Promise<ConverterResult> {
const key = cacheKey(base, symbols);
try {
return { status: "fresh", data: await getLatestRates(base, symbols) };
} catch (error) {
const previous = cache.get(key);
if (!previous) throw error;
return {
status: "last_known_good",
data: previous.value,
reason: error instanceof Error ? error.message : "Refresh failed",
};
}
} 429 응답에서는 Retry-After가 있으면 따르고 모든 인스턴스가 동시에 재시도하지 않게 한다. 일시 장애에는 무작위 지연을 포함한 지수 백오프가 적절하다. 인증 오류는 재시도가 아니라 설정 수정이 필요하다.
시장 휴장은 정상 상태다 market_session: weekend 응답이 거래 주간 마지막 관측값을 담는 것은 정상이다. 새 HTTP 응답 시각으로 관측 시각을 덮어쓰거나 주말 환율을 만들어 내지 않는다.
시장 휴장 중에는 앱 캐시 시간을 늘릴 수 있지만 거래 주간이 재개되면 다시 갱신해야 한다. 모든 통화가 한 일정에 따른다고 가정하지 말고 반환된 시장 상태와 제품 요구사항으로 결정한다.
환산 후 한 번만 반올림하기 기준 통화가 USD라면 25 USD를 EUR로 바꾸는 계산은 25 × rates.EUR이다. 환율 정밀도를 유지하고 최종 금액만 대상 통화 규칙에 따라 반올림한다.
참고용이며 체결가가 아니다
계산기 환율은 표시, 예상, 분석에 적합하다. 실제 청구액은 스프레드, 수수료, 반올림, 실행 시각을 포함한 결제 또는 거래 제공자의 값을 사용한다.
출시 전 확인 항목 화면에 필요한 통화만 요청한다. 기준 통화와 정렬한 심볼로 캐시 키를 만든다. 성공한 전체 응답만 저장하고 HTTP 오류는 저장하지 않는다. 동시 갱신 요청을 중복 실행하지 않는다. 데이터 나이가 중요하면 data_updated_at을 표시한다. sources, market_session, derived_symbols를 보존한다. API 키는 서버에 둔다. 마지막 정상값에는 원래 시각과 명확한 상태를 표시한다.