Integrar en tu web o app
Esta guía reúne lo necesario para usar la API en producción: llamarla desde el navegador (CORS), cachear hasta la próxima actualización con meta.next_update, mostrar la atribución y reintentar bien cuando la API pide esperar (429 o 503 con Retry-After).
Desde el navegador o desde tu servidor
Sección titulada «Desde el navegador o desde tu servidor»La API admite CORS desde cualquier origen (Access-Control-Allow-Origin: *, sin credenciales ni cookies), así que puedes llamarla directamente con fetch desde una página web. Los límites son por IP: si la llaman tus usuarios desde su navegador, cada uno tiene su cuota.
Si tu servidor hace las peticiones para muchos usuarios (una app con backend, un bot), todas salen de la misma IP y comparten los límites: en ese caso cachea en tu servidor y sirve desde ahí.
Las cabeceras que puedes leer desde JavaScript en otro origen son RateLimit-Policy, Retry-After y X-Request-Id (además de las simples, como Content-Type y Cache-Control).
Cachea hasta meta.next_update
Sección titulada «Cachea hasta meta.next_update»Los precios solo cambian dos veces al día (hacia las 8:00 y las 20:00, hora peninsular). Cada respuesta dice cuándo llegarán datos nuevos en meta.next_update, y las respuestas con precios llevan Cache-Control: public, max-age=… hasta esa hora (como mucho una hora, con stale-while-revalidate=300). Pedir lo mismo antes no te dará datos distintos.
- Guarda cada respuesta con la clave de su URL.
- Sírvela desde tu caché mientras la hora actual sea anterior a
meta.next_update(más un margen de unos minutos, porque la actualización tarda en completarse). - Después, vuelve a pedirla. Si
meta.last_updatedno ha cambiado, la actualización aún no ha terminado: reintenta en unos minutos.
Un cliente completo
Sección titulada «Un cliente completo»Con caché hasta next_update, reintentos con Retry-After y errores RFC 9457:
const API = "https://api.preciosgasolina.es/v1";const cache = new Map(); // url → { body, until }const MARGEN_MS = 10 * 60 * 1000; // la actualización tarda unos minutosconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export class ApiError extends Error { constructor(problem) { super(`${problem.code}: ${problem.detail}`); Object.assign(this, problem); // type, title, status, detail, code, request_id, instance }}
export async function api(path, params = {}, { intentos = 3 } = {}) { const url = API + path + (Object.keys(params).length ? "?" + new URLSearchParams(params) : ""); const hit = cache.get(url); if (hit && Date.now() < hit.until) return hit.body;
for (let i = 1; ; i++) { const res = await fetch(url, { headers: { Accept: "application/json" }, signal: AbortSignal.timeout(10_000) }); if (res.ok) { const body = await res.json(); const next = Date.parse(body.meta?.next_update); // Hasta la próxima actualización (+ margen); si no hay fecha, 10 minutos. const until = Number.isFinite(next) ? next + MARGEN_MS : Date.now() + MARGEN_MS; cache.set(url, { body, until }); return body; } const problem = await res.json().catch(() => ({ status: res.status, code: "HTTP_" + res.status, detail: res.statusText })); // Solo se reintenta lo que es temporal: 429 y 503 (con Retry-After) y errores 5xx. const temporal = res.status === 429 || res.status >= 500; if (!temporal || i >= intentos) { if (hit) return hit.body; // mejor un dato de la actualización anterior que nada throw new ApiError(problem); } const retryAfter = Number(res.headers.get("Retry-After")); await sleep((Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : 2 ** i) * 1000); }}
// Usoconst { data, meta } = await api("/prices", { level: "province", code: "28" });import timefrom datetime import datetime, timezone
import requests
API = "https://api.preciosgasolina.es/v1"MARGEN_S = 600 # la actualización tarda unos minutos_cache: dict[str, tuple[float, dict]] = {}_s = requests.Session()_s.headers["Accept"] = "application/json"
class ApiError(Exception): def __init__(self, problem: dict): super().__init__(f'{problem.get("code")}: {problem.get("detail")}') self.problem = problem # type, title, status, detail, code, request_id, instance
def api(path: str, intentos: int = 3, **params) -> dict: key = path + "?" + "&".join(f"{k}={v}" for k, v in sorted(params.items())) hit = _cache.get(key) if hit and time.time() < hit[0]: return hit[1] for i in range(1, intentos + 1): r = _s.get(API + path, params=params, timeout=10) if r.ok: body = r.json() nxt = body.get("meta", {}).get("next_update") until = datetime.fromisoformat(nxt.replace("Z", "+00:00")).timestamp() + MARGEN_S if nxt else time.time() + MARGEN_S _cache[key] = (until, body) return body temporal = r.status_code == 429 or r.status_code >= 500 if not temporal or i == intentos: if hit: return hit[1] # mejor la actualización anterior que nada try: raise ApiError(r.json()) except ValueError: raise ApiError({"status": r.status_code, "code": f"HTTP_{r.status_code}", "detail": r.reason}) retry_after = r.headers.get("Retry-After") time.sleep(int(retry_after) if retry_after and retry_after.isdigit() else 2**i) raise RuntimeError("inalcanzable")
body = api("/prices", level="province", code="28")Muestra la fuente y la fecha
Sección titulada «Muestra la fuente y la fecha»Debajo de los datos, muestra meta.attribution, la fecha de meta.last_updated en hora de Madrid y un enlace a meta.web_url. Es la condición de la licencia CC BY 4.0: Atribución y licencia.
meta.stale: datos de la actualización anterior
Sección titulada «meta.stale: datos de la actualización anterior»Si la base de datos no responde, la API sirve la última copia buena de esa respuesta con meta.stale: true y Cache-Control: public, max-age=60. Los datos son correctos pero pueden ser de la actualización anterior: puedes mostrarlos con la fecha de meta.last_updated y volver a pedirlos en un minuto. Ver Frescura de los datos.
Lista de comprobación
Sección titulada «Lista de comprobación»- Cacheas cada respuesta hasta
meta.next_update. - Muestras
meta.attribution, la fecha de los datos y un enlace ameta.web_url. - Reintentas solo
429,503y otros5xx, respetandoRetry-After. - Guardas
request_iden tus registros de error (y nos lo envías si nos escribes por un fallo). - Usas
limit(máximo 50) y no recorres toda España petición a petición: para eso están los datos abiertos. - No pides parámetros que el endpoint no admite: la API responde
400a los parámetros desconocidos.
Fuente de los datos: Ministerio para la Transición Ecológica y el Reto Demográfico (Geoportal de Gasolineras). Datos de la API y del servidor MCP con licencia CC BY 4.0: cita Gasolina hoy y la fuente.
© 2026 ONETOUCH SA de CVpreciosgasolina.esTérminos de usoAviso legalPrivacidadllms.txtOpenAPI