API para desarrolladores

Los mismos datos que el mapa, en JSON. Gratis, sin registro y sin clave. Úsala en tu web, tu app o tu investigación.

En un minuto

  • Dirección base: https://trenes.valfiguer.com/api/v1
  • Solo lectura (GET), respuestas en JSON o GeoJSON, en UTF-8 y comprimidas si las pides con gzip.
  • CORS abierto: puedes llamarla desde el navegador, desde cualquier dominio. No usa cookies ni clave.
  • Horas como marca Unix en segundos (*_ts) y como ISO 8601 en UTC (*_at).
  • Especificación OpenAPI: /api/openapi.json · Consola interactiva: /api/docs
curl -s --compressed https://trenes.valfiguer.com/api/v1/trains | jq '.features | length'

Condiciones de uso

Los datos vienen de Renfe Data, FGC Dades Obertes y Open Data Euskadi (MovEuskadi), todos con licencia CC BY 4.0. Puedes usarlos para lo que quieras, también con fines comerciales, si citas la fuente. Una línea basta:

Datos: Renfe Data, FGC y MovEuskadi (CC BY 4.0) vía trenes.valfiguer.com

No es un sistema de seguridad. Las posiciones llegan con segundos o minutos de retraso y pueden faltar trenes. No la uses para decisiones de seguridad ni la presentes como información oficial; la oficial es la de cada operador y Adif.

Límites y caché

  • Hasta 30 peticiones por segundo por IP, con ráfagas de 100. Por encima responde 429: espera un segundo y reintenta.
  • Las posiciones se renuevan cada 15–20 s en origen. Pedir /trains más de una vez cada 5 s no trae nada nuevo.
  • Las respuestas grandes llevan ETag: envía If-None-Match y recibirás 304 sin cuerpo si no ha cambiado.
  • Si vas a mover mucho tráfico o necesitas el histórico completo, escríbenos a info@valfiguer.com.

Cómo leer un tren

  • delay_s: retraso en segundos según el operador; null si no lo publica (FGC solo dice si va en hora: mira flags).
  • reported_at / age_s: cuándo se tomó la posición y su antigüedad al generar la respuesta. stale: true si lleva más de 3 minutos sin actualizarse.
  • flags: correcciones o avisos aplicados al dato, por ejemplo una hora corregida o una posición descartada por imposible.
  • category: ld (alta velocidad y larga distancia), md (media distancia), cercanias u otros (FGC y Metro Bilbao).

Rutas

Trenes en circulación

GET/api/v1/trains

Todos los trenes con posición publicada ahora, como GeoJSON FeatureCollection, con el estado de cada fuente en feeds.

Caché: 5 s, ETag · Probar

{
  "type": "FeatureCollection",
  "generated_at": "2026-11-01T08:12:30Z",
  "generated_ts": 1793520750,
  "attribution": "Fuentes: Renfe Data (data.renfe.com), FGC Dades Obertes … CC BY 4.0. …",
  "feeds": [{ "id": "renfe_ld_positions", "ok": true, "stale": false, "data_age_s": 14 }],
  "features": [{
    "type": "Feature",
    "id": "renfe_ld:<id>",
    "geometry": { "type": "Point", "coordinates": [-3.6893, 40.4066] },
    "properties": {
      "source": "renfe_ld", "operator": "Renfe", "category": "ld",
      "product": "AVE", "train_number": "00297", "nucleo": null,
      "line_color": null, "origin": "Madrid-Puerta de Atocha", "destination": "Barcelona-Sants",
      "status_text": "en", "stop_name": "Zaragoza-Delicias", "platform": "4",
      "delay_s": 180, "reported_at": "2026-11-01T08:12:16Z", "age_s": 14,
      "stale": false, "speed_kmh": 287, "bearing": 64, "flags": []
    }
  }]
}

Un tren

GET/api/v1/trains/{id}

Un tren con su recorrido reciente (hasta 80 posiciones en track: lon, lat, hora) y su itinerario: paradas con hora programada y estimada (horario más el retraso actual).

ParámetroEnQué es
idrutaEl id de /trains, por ejemplo renfe_cercanias:1004D27203C4a.

Caché: 5 s · Probar

{
  "id": "renfe_ld:00297",
  "properties": { "…": "como en /trains" },
  "track": [[-3.6893, 40.4066, 1793520736]],
  "itinerary": {
    "method": "horario_mas_retraso_actual",
    "delay_s": 180,
    "stops": [{
      "code": "71801", "name": "Barcelona-Sants",
      "arrival_ts": 1793530800, "estimated_arrival_ts": 1793530980,
      "state": "upcoming"
    }]
  }
}

Un tren por su número

GET/api/v1/trains/numero/{número}

Lo mismo que /trains/{id}, buscando por el número comercial que sale en el billete o en el panel. Responde 404 si ese número no tiene posición ahora.

ParámetroEnQué es
númerorutaNúmero comercial, con o sin ceros a la izquierda.
nucleoconsultaOpcional. Distingue Cercanías de ciudades distintas: Madrid, Barcelona, Valencia…

Caché: 5 s · Probar

{ "id": "renfe_cercanias:1004D27704C5", "properties": { "…": "…" }, "track": [], "itinerary": null }

Horario de hoy de un tren

GET/api/v1/horario/numero/{número}

Las salidas de hoy de un número de tren según el calendario GTFS de Renfe, con sus paradas y si está cancelado. Sirve para trenes que aún no han salido.

ParámetroEnQué es
númerorutaNúmero comercial.
nucleoconsultaOpcional, como arriba.

Caché: 30 s · Probar

{
  "method": "horario_gtfs",
  "runs": [{
    "trip_id": "0029712026-11-01", "day": "2026-11-01", "cancelled": false,
    "product": "AVE", "origin": "Madrid-Puerta de Atocha", "destination": "Barcelona-Sants",
    "departure_ts": 1793520000, "arrival_ts": 1793530800,
    "itinerary": { "stops": [] }
  }]
}

Panel de una estación

GET/api/v1/stations/{código}

Trenes de las próximas dos horas en una estación: los que circulan, con hora estimada, y los que aún no han salido según el horario de hoy (state: scheduled). Incluye la ficha de la estación cuando Adif la publica.

ParámetroEnQué es
códigorutaCódigo de estación de Renfe (hasta 5 cifras), el mismo de /stations.

Caché: 10 s · Probar

{
  "station": { "code": "18000", "name": "Madrid-Puerta de Atocha", "lat": 40.4066, "lon": -3.6893 },
  "trains": [{
    "id": null, "trip_id": "…", "state": "scheduled", "category": "cercanias",
    "product": "C5", "train_number": "27704", "destination": "Humanes",
    "planned_ts": 1793520660, "estimated_ts": null, "delay_s": null,
    "cancelled": false, "terminates": false
  }]
}

Estaciones

GET/api/v1/stations

Todas las estaciones con su código y nombre, como GeoJSON de puntos.

Caché: 1 h, ETag · Probar

{ "type": "FeatureCollection", "features": [{ "geometry": { "type": "Point", "coordinates": [-3.6893, 40.4066] }, "properties": { "code": "18000", "name": "Madrid-Puerta de Atocha" } }] }

Líneas

GET/api/v1/lines

Recorrido de las líneas de Cercanías, FGC y Metro Bilbao con su color oficial, como GeoJSON de líneas simplificadas a unos 15 m.

Caché: 1 h, ETag · Probar

{ "type": "FeatureCollection", "features": [{ "properties": { "nucleo": "Madrid", "line": "C5", "name": "Móstoles-El Soto - Humanes", "color": "#FFCB06", "text_color": "#000000" }, "geometry": { "type": "LineString", "coordinates": [] } }] }

Puntualidad

GET/api/v1/puntualidad

Puntualidad diaria por línea y producto: viajes, viajes con retraso y suma de retrasos, a partir del último retraso publicado de cada tren.

ParámetroEnQué es
diasconsultaDías hacia atrás, de 1 a 31 (7 por defecto).

Caché: 5 min · Probar

{ "on_time_threshold_s": 300, "days": [{ "date": "2026-10-31", "partial": false, "groups": [{ "label": "C5", "nucleo": "Madrid", "trips": 412, "on_time": 371, "over_30": 2 }] }] }

Avisos

GET/api/v1/alerts

Avisos activos de Cercanías publicados por Renfe, con las líneas a las que afectan.

Caché: 30 s · Probar

{ "alerts": [{ "id": "…", "text": "…", "start": "2026-11-01T05:00:00Z", "end": null, "lines": [{ "nucleo": "Madrid", "line": "C3" }] }] }

Cancelaciones

GET/api/v1/cancellations

Trenes que Renfe da por cancelados ahora.

Caché: 30 s · Probar

{ "count": 1, "trains": [{ "trip_id": "…", "category": "md", "product": "MD", "train_number": "18073", "origin": "…", "destination": "…" }] }

Estado

GET/api/v1/status

Resumen del servicio: número de trenes, puntualidad del momento y salud de cada fuente.

Caché: 10 s · Probar

{ "trains": { "total": 812, "fresh": 790, "by_category": { "ld": 120, "cercanias": 610 } }, "delay": { "known": 700, "on_time_share": 0.91 }, "cancelled": 11, "feeds": [{ "id": "renfe_ld_positions", "ok": true, "data_age_s": 14 }] }

Ejemplos

JavaScript, en el navegador o en Node 18+:

const res = await fetch("https://trenes.valfiguer.com/api/v1/trains");
const { features } = await res.json();
const late = features.filter((f) => (f.properties.delay_s ?? 0) > 600);
console.log(`${late.length} trenes con más de 10 min de retraso`);

Python:

import httpx

board = httpx.get("https://trenes.valfiguer.com/api/v1/stations/18000").json()
for t in board["trains"][:5]:
    print(t["product"], t["destination"], t["delay_s"])

Mapa con MapLibre o Leaflet: añade la respuesta de /trains como fuente GeoJSON y refréscala cada 15 s.

map.addSource("trenes", { type: "geojson", data: "https://trenes.valfiguer.com/api/v1/trains" });
setInterval(() => map.getSource("trenes").setData("https://trenes.valfiguer.com/api/v1/trains"), 15000);

Versiones

La v1 es estable: podemos añadir campos, pero no quitarlos ni cambiar su significado sin publicar una v2 y mantener la v1 al menos seis meses. Tu código debe ignorar los campos que no conozca.

Más sobre las fuentes, su frecuencia y el registro histórico en Datos.

Widgets para tu web

Un panel de salidas de una estación o los trenes de una línea, dentro de tu página. Se actualizan solos y ajustan su altura al contenido. Pega el bloque donde quieras que aparezca:

<div data-trenes-estacion="18000"></div>
<script src="https://trenes.valfiguer.com/embed.js" async></script>

Una línea: núcleo y línea, tal como aparecen en el mapa, en minúsculas y con guiones.

<div data-trenes-linea="madrid/c5"></div>
<div data-trenes-linea="rodalies-de-catalunya/r2-nord"></div>
<div data-trenes-linea="fgc/s1"></div>
<script src="https://trenes.valfiguer.com/embed.js" async></script>
  • data-tema="claro" u "oscuro": fija el tema. Sin él, sigue el del visitante.
  • data-filas="6": cuántos trenes mostrar (estación: 1–15, 6 por defecto; línea: 1–20, 8 por defecto).
  • El código de estación es el de Renfe, el mismo de /stations/{código} y de la dirección de cada página de estación.
  • Si añades widgets después de cargar la página (una aplicación de una sola página), llama a window.TrenesEmbed.scan().

Sin JavaScript, con un iframe de altura fija:

<iframe src="https://trenes.valfiguer.com/embed/estacion/18000?tema=claro&filas=5"
        title="Próximos trenes" loading="lazy"
        style="width:100%;height:360px;border:0"></iframe>

El pie del widget lleva la atribución que piden las licencias (Renfe Data, FGC y MovEuskadi, CC BY 4.0) y un enlace a la página completa: no lo tapes ni lo quites. Solo las rutas /embed/ se pueden mostrar dentro de otras webs.

Panel a pantalla completa

Para una televisión o una tableta en un vestíbulo, una oficina o una cafetería: /panel/{código} muestra las próximas salidas en grande, con reloj, se actualiza cada 15 s, ajusta el tamaño de la letra a la pantalla y, donde el navegador lo permite, impide que la pantalla se apague.

https://trenes.valfiguer.com/panel/18000
https://trenes.valfiguer.com/panel/18000?tema=claro&filas=8

Oscuro por defecto; ?tema=claro para salas con mucha luz. ?filas= admite de 3 a 20. Cada página de estación tiene un enlace «Modo panel».

Cliente JavaScript

Un módulo pequeño, sin dependencias, para el navegador o Node 18+. Tipos en /sdk/trenes.d.ts.

import { createClient } from "https://trenes.valfiguer.com/sdk/trenes.js";

const api = createClient(); // o createClient({ baseUrl, timeoutMs, fetch })
const { features } = await api.trains();
const board = await api.station("18000"); // null si el código no existe
const runs = await api.schedule("00297");
const tren = await api.train(features[0].id);
const lineas = await api.lines();

También exporta cada función suelta (trains, train, station, schedule, lines…). Un 404 devuelve null; cualquier otro error lanza TrenesError con su status. Para TypeScript, descarga el .d.ts junto al módulo o copia los tipos que necesites.

Python 3.10+, solo con la biblioteca estándar:

import json
import urllib.request

BASE = "https://trenes.valfiguer.com/api/v1"

def get(path: str):
    req = urllib.request.Request(BASE + path, headers={"Accept": "application/json"})
    with urllib.request.urlopen(req, timeout=10) as res:
        return json.load(res)

board = get("/stations/18000")
print(board["station"]["name"])
for t in board["trains"][:8]:
    retraso = "sin dato" if t["delay_s"] is None else f"{round(t['delay_s'] / 60)} min"
    print(t["product"], t["train_number"], "→", t["destination"], retraso)

# Datos: Renfe Data, FGC y MovEuskadi (CC BY 4.0) vía trenes.valfiguer.com

Feeds RSS

Avisos y cancelaciones en formato RSS, para tu lector de noticias o para automatizar alertas. Se pueden filtrar por núcleo y por línea con ?nucleo=madrid&linea=c5; cada página de línea enlaza los suyos.

Reflejan lo que Renfe publica en sus datos abiertos, con sus mismos huecos. Licencia CC BY 4.0.