En el navegador puedes empezar con una llamada anónima. En Node.js o en el servidor, guarda la clave en una variable de entorno y conserva la información de frescura que acompaña al tipo.
- fetch funciona en Node 18+, Deno, Bun y navegadores modernos.
- No pongas una clave de cuenta en JavaScript que se envía al navegador.
- Usa currency.js para aritmética y formato, no para obtener la cotización.
- Comprueba la respuesta HTTP y respeta Retry-After cuando recibas 429.
- Muestra source y effective_at cuando el usuario necesite saber la antigüedad.
Tu primera llamada, sin clave
En Node 18 o en cualquier navegador moderno ya tienes fetch. Un solo await basta para leer el tipo del EUR:
Las llamadas anónimas tienen tope por dirección IP, así que pásate a una clave gratuita en cuanto dejes atrás la prueba rápida.
Añade una clave y manejo de errores
Pasa la clave como token bearer en la cabecera Authorization. Comprueba res.ok y lanza en un estado malo para que los errores salgan a la superficie pronto:
La cabecera X-API-Key: exr_live_... también se acepta, para plataformas que no pueden fijar una cabecera Authorization (algunos entornos serverless, Zapier y herramientas no-code parecidas).
Convierte un importe
Para una conversión directa llama a /v1/convert/{from}/{to}/{amount}. La respuesta te da el tipo y el importe convertido en un solo viaje:
El endpoint de conversión devuelve los mismos campos source y market_session que el endpoint latest, así que puedes mostrar a los usuarios la clase de tipo y si la semana interbancaria está abierta.
Entiende source y market_session
Para la moneda que muestras, consulta sources[currency] y effective_at[currency]: indican el tipo de fuente y la hora de observación. source resume los datos menos recientes de toda la respuesta. market_session describe la sesión de mercado; timestamp es la hora de generación de la respuesta, no la hora de observación.
| Valor del campo | Qué significa | Cuándo se mueve |
|---|---|---|
| source: live | Tipo spot indicativo | Intradía (~60 s), semana de negociación |
| source: ecb_daily | Fix de referencia del Banco Central Europeo | Una vez por día hábil (~16:00 CET) |
| source: market_daily | Cierre guardado sin BCE | Feed diario |
| market_session: weekend | Sábado o domingo (interbancario cerrado) | Los fixes de referencia arrastran el valor del viernes; el feed en vivo no se actualiza |
El campo data_updated_at te dice cuándo se escribió por última vez el tipo subyacente. timestamp registra cuándo se construyó la respuesta. Juntos te permiten decidir cómo cachear y mostrar el valor.
Estos tipos se publican para referencia, análisis y visualización. No son una cotización en firme y no deben usarse para liquidar una operación ni una transferencia. Cada respuesta lo declara en su campo notice.
Límites del plan gratuito
La cuota mensual se reinicia el día 1 de cada mes a las 00:00 UTC. Ante un 429, respeta Retry-After antes de reintentar.
- 5,000 llamadas al mes
- 10 peticiones por minuto
- Sin tarjeta de crédito
- Cubre
/v1/latest,/v1/convert,/v1/rangey/v1/{date}/{base}
Calcula y formatea el importe
currency.js ayuda con el importe, los decimales y el formato. No sustituye a una API de FX: primero obtén el tipo y después multiplica el importe.
const response = await fetch("https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP", { signal: AbortSignal.timeout(10_000),});if (!response.ok) throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);const data = await response.json();console.log(data.rates.EUR);console.log(data.sources.EUR, data.effective_at.EUR);console.log(data.market_session);// Node.js server only. Never put an account key in browser code.const KEY = process.env.EXCHANGERATE_API_KEY;if (!KEY) throw new Error("Set EXCHANGERATE_API_KEY on your server");async function getLatest(base = "USD") { const response = await fetch(`https://api.exchangerate.dev/v1/latest/${encodeURIComponent(base)}`, { headers: { Authorization: `Bearer ${KEY}` }, redirect: "error", signal: AbortSignal.timeout(10_000), }); if (!response.ok) throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`); return response.json();}const data = await getLatest("USD");console.log(data.rates.GBP, data.effective_at.GBP);// Node.js server only.const KEY = process.env.EXCHANGERATE_API_KEY;if (!KEY) throw new Error("Set EXCHANGERATE_API_KEY on your server");const response = await fetch("https://api.exchangerate.dev/v1/convert/USD/EUR/100", { headers: { Authorization: `Bearer ${KEY}` }, redirect: "error", signal: AbortSignal.timeout(10_000),});if (!response.ok) throw new Error(`HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);const data = await response.json();console.log(data.rate, data.converted);import currency from "currency.js";const response = await fetch("https://api.exchangerate.dev/v1/latest/USD?symbols=EUR", { signal: AbortSignal.timeout(10_000),});if (!response.ok) throw new Error(`HTTP ${response.status}`);const data = await response.json();const rate = data.rates.EUR;if (!Number.isFinite(rate) || rate <= 0) throw new Error("Invalid rate");const euros = currency("100.00", { precision: 2, symbol: "€" }).multiply(rate);console.log(euros.format());