API para desenvolvedores

Os mesmos datos que o mapa, en JSON. De balde, sen rexistro e sen clave. Úsaa na túa web, na túa app ou na túa investigación.

Nun minuto

  • Enderezo base: https://trenes.valfiguer.com/api/v1
  • Só lectura (GET), respostas en JSON ou GeoJSON, en UTF-8 e comprimidas se as pides con gzip.
  • CORS aberto: podes chamala desde o navegador, desde calquera dominio. Non usa cookies nin clave.
  • Horas como marca Unix en segundos (*_ts) e 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'

Condicións de uso

Os datos veñen de Renfe Data, FGC Dades Obertes e Open Data Euskadi (MovEuskadi), todos con licenza CC BY 4.0. Podes usalos para o que queiras, tamén con fins comerciais, se citas a fonte. Abonda cunha liña:

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

Non é un sistema de seguridade. As posicións chegan con segundos ou minutos de atraso e poden faltar trens. Non a uses para decisións de seguridade nin a presentes como información oficial; a oficial é a de cada operador e Adif.

Límites e caché

  • Ata 30 peticións por segundo por IP, con picos de 100. Por riba responde 429: agarda un segundo e téntao de novo.
  • As posicións renóvanse cada 15–20 s na orixe. Pedir /trains máis dunha vez cada 5 s non trae nada novo.
  • As respostas grandes levan ETag: envía If-None-Match e recibirás 304 sen corpo se non cambiou.
  • Se vas mover moito tráfico ou necesitas o histórico completo, escríbenos a info@valfiguer.com.

Como ler un tren

  • delay_s: atraso en segundos segundo o operador; null se non o publica (FGC só di se vai en hora: mira flags).
  • reported_at / age_s: cando se tomou a posición e a súa antigüidade ao xerar a resposta. stale: true se leva máis de 3 minutos sen actualizarse.
  • flags: correccións ou avisos aplicados ao dato, por exemplo unha hora corrixida ou unha posición descartada por imposible.
  • category: ld (alta velocidade e longa distancia), md (media distancia), cercanias ou otros (FGC e Metro Bilbao).

Rutas

Trens en circulación

GET/api/v1/trains

Todos os trens con posición publicada agora, como GeoJSON FeatureCollection, co estado de cada fonte 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 co seu percorrido recente (ata 80 posicións en track: lon, lat, hora) e o seu itinerario: paradas con hora programada e estimada (horario máis o atraso actual).

ParámetroEnQue é
idrutaO id de /trains, por exemplo 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 polo seu número

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

O mesmo que /trains/{id}, buscando polo número comercial que sae no billete ou no panel. Responde 404 se ese número non ten posición agora.

ParámetroEnQue é
númerorutaNúmero comercial, con ou sen ceros á esquerda.
nucleoconsultaOpcional. Distingue Cercanías de cidades distintas: Madrid, Barcelona, Valencia…

Caché: 5 s · Probar

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

Horario de hoxe dun tren

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

As saídas de hoxe dun número de tren segundo o calendario GTFS de Renfe, coas súas paradas e se está cancelado. Serve para trens que aínda non saíron.

ParámetroEnQue é
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 dunha estación

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

Trens das próximas dúas horas nunha estación: os que circulan, con hora estimada, e os que aínda non saíron segundo o horario de hoxe (state: scheduled). Inclúe a ficha da estación cando Adif a publica.

ParámetroEnQue é
códigorutaCódigo de estación de Renfe (ata 5 cifras), o mesmo 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
  }]
}

Estacións

GET/api/v1/stations

Todas as estacións co seu código e nome, 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" } }] }

Liñas

GET/api/v1/lines

Percorrido das liñas de Cercanías, FGC e Metro Bilbao coa súa cor oficial, como GeoJSON de liñas simplificadas a uns 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": [] } }] }

Puntualidade

GET/api/v1/puntualidad

Puntualidade diaria por liña e produto: viaxes, viaxes con atraso e suma de atrasos, a partir do último atraso publicado de cada tren.

ParámetroEnQue é
diasconsultaDías cara 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, coas liñas ás que afectan.

Caché: 30 s · Probar

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

Cancelacións

GET/api/v1/cancellations

Trens que Renfe dá por cancelados agora.

Caché: 30 s · Probar

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

Estado

GET/api/v1/status

Resumo do servizo: número de trens, puntualidade do momento e saúde de cada fonte.

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 }] }

Exemplos

JavaScript, no navegador ou 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} trens con máis de 10 min de atraso`);

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 ou Leaflet: engade a resposta de /trains como fonte GeoJSON e actualízaa 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);

Versións

A v1 é estable: podemos engadir campos, pero non quitalos nin cambiar o seu significado sen publicar unha v2 e manter a v1 polo menos seis meses. O teu código debe ignorar os campos que non coñeza.

Máis sobre as fontes, a súa frecuencia e o rexistro histórico en Datos.

Widgets para a túa web

Un panel de saídas dunha estación ou os trens dunha liña, dentro da túa páxina. Actualízanse sós e axustan a súa altura ao contido. Pega o bloque onde queiras que apareza:

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

Unha liña: núcleo e liña, tal como aparecen no mapa, en minúsculas e con guións.

<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" (claro) ou "oscuro" (escuro): fixa o tema. Sen el, segue o do visitante. O atributo e os seus valores quedan en castelán.
  • data-filas="6": cantos trens mostrar (estación: 1–15, 6 por defecto; liña: 1–20, 8 por defecto).
  • data-idioma="gl": idioma do widget (es, en, ca, eu ou gl; castelán por defecto). O panel e o widget de pantalla de inicio tamén existen en cada idioma: /gl/panel/18000.
  • O código de estación é o de Renfe, o mesmo de /stations/{código} e do enderezo de cada páxina de estación.
  • Se engades widgets despois de cargar a páxina (unha aplicación dunha soa páxina), chama a window.TrenesEmbed.scan().

Sen JavaScript, cun iframe de altura fixa:

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

O pé do widget leva a atribución que piden as licenzas (Renfe Data, FGC e MovEuskadi, CC BY 4.0) e unha ligazón á páxina completa: non o tapes nin o quites. Só as rutas /embed/ se poden mostrar dentro doutras webs.

Panel a pantalla completa

Para unha televisión ou unha tableta nun vestíbulo, unha oficina ou unha cafetería: /panel/{código} mostra as próximas saídas en grande, con reloxo, actualízase cada 15 s, axusta o tamaño da letra á pantalla e, onde o navegador o permite, impide que a pantalla se apague.

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

Escuro por defecto; ?tema=claro para salas con moita luz. ?filas= admite de 3 a 20. Cada páxina de estación ten unha ligazón «Modo panel».

Cliente JavaScript

Un módulo pequeno, sen dependencias, para o navegador ou Node 18+. Tipos en /sdk/trenes.d.ts.

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

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

Tamén exporta cada función solta (trains, train, station, schedule, lines…). Un 404 devolve null; calquera outro erro lanza TrenesError co seu status. Para TypeScript, descarga o .d.ts xunto ao módulo ou copia os tipos que necesites.

Python 3.10+, só coa 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]:
    atraso = "sen dato" if t["delay_s"] is None else f"{round(t['delay_s'] / 60)} min"
    print(t["product"], t["train_number"], "→", t["destination"], atraso)

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

Feeds RSS

Avisos e cancelacións en formato RSS, para o teu lector de noticias ou para automatizar alertas. Pódense filtrar por núcleo e por liña con ?nucleo=madrid&linea=c5; cada páxina de liña ten ligazóns aos seus.

Reflicten o que Renfe publica nos seus datos abertos, cos mesmos ocos. Licenza CC BY 4.0. Os textos dos avisos están en castelán, tal como os publica Renfe.