# Integrar en tu web o app

> Versión Markdown de https://docs.preciosgasolina.es/guias/integrar-en-tu-web/ · Actualizado: 2026-10-07 · Índice para LLM: https://docs.preciosgasolina.es/llms.txt

CORS, caché hasta meta.next_update, atribución, errores y reintentos con Retry-After: cómo integrar la API de Gasolina hoy en producción, con código.

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

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](https://docs.preciosgasolina.es/conceptos/limites/): 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`

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.

1. Guarda cada respuesta con la clave de su URL.
2. 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).
3. Después, vuelve a pedirla. Si `meta.last_updated` no ha cambiado, la actualización aún no ha terminado: reintenta en unos minutos.

## Un cliente completo

Con caché hasta `next_update`, reintentos con `Retry-After` y errores RFC 9457:

**JavaScript**

```js
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 minutos
const 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);
  }
}

// Uso
const { data, meta } = await api("/prices", { level: "province", code: "28" });
```

**Python**

```python
import time
from 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")
```

> **No reintentes los errores 4xx**
>
> Un `400` (parámetro mal escrito), un `404` (no existe) o un `405` no se arreglan reintentando: corrige la petición. Solo `429` y `503` llevan `Retry-After`; respeta ese tiempo antes de volver a intentarlo. Más en [Errores](https://docs.preciosgasolina.es/conceptos/errores/).

## 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](https://docs.preciosgasolina.es/empezar/atribucion-y-licencia/).

## `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](https://docs.preciosgasolina.es/conceptos/frescura/).

## Lista de comprobación

- [ ] Cacheas cada respuesta hasta `meta.next_update`.
- [ ] Muestras `meta.attribution`, la fecha de los datos y un enlace a `meta.web_url`.
- [ ] Reintentas solo `429`, `503` y otros `5xx`, respetando `Retry-After`.
- [ ] Guardas `request_id` en 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](https://preciosgasolina.es/datos/).
- [ ] No pides parámetros que el endpoint no admite: la API responde `400` a los parámetros desconocidos.
