API pública
API pública de Andalucía Mesh: estado de la malla, provincias, rankings, routers y alertas en JSON, sin registro, y webhooks firmados.
Todos los datos públicos de Andalucía Mesh en JSON: estado de la malla, nodos y carga por provincia, rankings, routers y alertas. Es la misma API que usan este portal y los bots. Es de solo lectura y no necesita registro.
Lo básico
| URL base | https://mesh.desdechipiona.es/api/v1 [Copiar] |
| Métodos | Solo GET |
| Autenticación | Ninguna |
| Formato | JSON |
| Límite | 60 peticiones por minuto por IP |
| CORS | Abierto para GET |
| Fechas | ISO 8601 en UTC. Los días, semanas y meses de los rankings se cortan en hora de España |
| Provincias | Códigos ISO 3166-2: ES-AL, ES-CA, ES-CO, ES-GR, ES-H, ES-J, ES-MA, ES-SE |
| Tiempo real | No hay. Para enterarte al momento usa los bots o los webhooks. Si consultas de forma periódica, no lo hagas más de una vez cada 30 segundos |
Todas las respuestas llevan:
generated_at: cuándo se generaron los datos.- La ventana o el periodo que cubren.
notes: advertencias para interpretarlos (por ejemplo, que solo cuentan los nodos con OK to MQTT).stale: truesi la fuente no responde y te servimos la última copia guardada.
Lo que no vas a encontrar: coordenadas exactas. La API da recuentos, nombres públicos, ids y provincia, nunca la ubicación de un nodo.
Endpoints
| Ruta | Devuelve | Se actualiza cada |
|---|---|---|
GET /stats/summary |
Estado general de la malla | 60 s |
GET /stats/provinces |
Nodos y carga del canal por provincia, y total de Andalucía | 60 s |
GET /stats/rankings |
Catálogo de rankings | 1 h |
GET /stats/rankings/{id} |
Un ranking | De 60 s a 15 min, según el periodo |
GET /stats/traffic-mix |
Reparto del tráfico por tipo de paquete | 5 min |
GET /routers |
Routers con batería, utilización del canal y tiempo de transmisión | 60 s |
GET /alerts |
Alertas, con filtros | 30 s |
GET /alerts/{id} |
Una alerta con su historial | 30 s |
GET /alerts/catalog |
Riesgos, tipos y reglas activas | 1 h |
GET /nodes/at-risk |
Nodos en peligro | 30 s |
Detalle
Estado general · GET /stats/summary
Nodos activos en 24 h y en 7 días, routers activos en 24 h, gateways publicando, paquetes por hora, utilización media del canal en Andalucía y por provincia, y alertas abiertas por riesgo y por tipo.
{
"generated_at": "2026-10-02T12:00:00Z",
"nodes_active_24h": 412,
"nodes_active_7d": 655,
"routers_active_24h": 38,
"gateways_publishing": 9,
"packets_per_hour": 15840,
"channel_utilization": {
"andalucia_avg": 18.6,
"provinces": [{"code": "ES-CA", "avg": 31.2, "level": "orange"}]
},
"alerts_open": {
"by_risk": {"bajo": 4, "medio": 7, "alto": 1},
"by_type": {"infraestructura": 9, "clientes": 3}
},
"notes": ["Solo nodos cuyos paquetes llegan con OK to MQTT"]
}
Cifras de ejemplo.
Nodos y carga por provincia · GET /stats/provinces
| Parámetro | Valores |
|---|---|
window |
24h, 7d (por defecto) o 30d. Otro valor → 400 |
- Nodos: un nodo cuenta en la provincia donde cae su última posición válida recibida dentro de la ventana. No cuentan los nodos sin posición, las posiciones
0,0ni las que tienen un error de más de unos 12 km. - Carga (
load): saturación estimada del canal en las últimas 12 horas, sea cual sea la ventana. Se toman los nodos situados en la provincia y se separan en routers (ROUTER,ROUTER_LATE,REPEATER) y clientes (CLIENTyCLIENT_BASEjuntos) (losCLIENT_MUTEno cuentan).avg= 0,6 × media de routers + 0,4 × media conjunta deCLIENTyCLIENT_BASE(si falta un grupo, su peso se reparte entre los demás).maxes el valor más alto ygroupstrae la media y el número de nodos de cada grupo. - Nivel (
level):green,orange,redonodata, con los cortes deload_levels. outside_andalucia: nodos oídos desde Andalucía pero situados fuera.border_uncertain: nodos cuya posición imprecisa toca el límite entre dos provincias (se asignan por su punto central).
{
"window": "7d",
"load_window": "12h",
"generated_at": "2026-10-01T16:00:00Z",
"total_andalucia": 412,
"outside_andalucia": 37,
"load_levels": {"green_max": 20, "red_min": 40},
"provinces": [
{"code": "ES-CA", "name": "Cádiz", "nodes": 138, "load": {"avg": 27.4, "max": 44.5, "level": "orange",
"groups": {"routers": {"avg": 31.2, "n": 9}, "clients": {"avg": 22.0, "n": 16}}}},
{"code": "ES-CO", "name": "Córdoba", "nodes": 19, "load": {"avg": null, "max": null, "level": "nodata",
"groups": {"routers": {"avg": null, "n": 0}, "clients": {"avg": null, "n": 0}}}}
],
"border_uncertain": 5,
"notes": [
"Solo nodos cuya posición ha llegado a nuestros gateways con OK to MQTT activado.",
"Saturación estimada: media de routers (60 %) y media conjunta de CLIENT y CLIENT_BASE (40 %), últimas 12 h; CLIENT_MUTE no cuenta."
]
}
Cifras de ejemplo, recortado a dos provincias.
Rankings · GET /stats/rankings y GET /stats/rankings/{id}
/stats/rankings devuelve el catálogo. Cada ranking se pide por su id:
| Parámetro | Valores |
|---|---|
period |
hour, day, week o month |
which |
current (en curso) o previous (el anterior, ya cerrado) |
limit |
10 por defecto, 50 como máximo |
period |
current |
previous |
|---|---|---|
hour |
La hora en curso | La última hora completa |
day |
Hoy | Ayer |
week |
La semana en curso (ISO) | La semana anterior |
month |
El mes en curso | El mes anterior |
Los periodos cerrados no cambian.
id |
Qué mide |
|---|---|
network-usage |
Tiempo de aire de los paquetes que origina cada nodo (no los que retransmite), con número de paquetes y desglose por tipo |
gateway-coverage |
Nodos distintos que oye cada gateway |
gateway-exclusive |
Paquetes que solo ha subido ese gateway |
longest-links |
Distancia en km de los enlaces directos (sin saltos) entre nodo y gateway |
best-links |
SNR medio de los enlaces directos con al menos 10 recepciones |
most-neighbors |
Vecinos directos distintos |
uptime |
Mayor tiempo encendido sin reiniciarse |
solar-health |
Batería mínima más alta del periodo |
chatters |
Mensajes de texto en los canales públicos |
new-nodes |
Nodos vistos por primera vez en el periodo |
provinces-growth |
Diferencia de nodos activos frente al periodo anterior, por provincia |
Para no premiar la casualidad, se excluyen los nodos con pocos datos en el periodo. En network-usage, cada paquete cuenta una vez aunque lo oigan varios gateways.
{
"id": "network-usage",
"period": "day",
"which": "previous",
"from": "2026-09-30T22:00:00Z",
"to": "2026-10-01T22:00:00Z",
"items": [
{
"rank": 1,
"node": {"id": "!a1b2c3d4", "short": "XYZ1", "long": "Nodo ejemplo", "province": "ES-CA"},
"value": 412.6, "unit": "s",
"share_pct": 3.8,
"packets": 1290,
"by_type": {"nodeinfo": 61.0, "position": 22.5, "telemetry": 15.1, "text": 0.4, "other": 1.0}
}
],
"notes": ["Tiempo de aire estimado de paquetes originados por el nodo; no incluye retransmisiones."]
}
Datos de ejemplo.
Reparto del tráfico · GET /stats/traffic-mix
Qué parte del tráfico es cada tipo de paquete (mensajes, información del nodo, posición, telemetría y otros) en el periodo pedido. Parámetro: period (por ejemplo, day).
Routers · GET /routers
Todos los nodos con rol ROUTER, ROUTER_LATE o REPEATER vistos en los últimos 7 días.
| Parámetro | Valores |
|---|---|
province |
Código de provincia |
sort |
battery, chutil, tx o last_seen |
chutil: utilización del canal (%).tx: porcentaje de tiempo que transmite el propio router. Último dato de las últimas 12 h;nullsi no hay.battery.poweredestruecuando el nodo está alimentado por red (informa un nivel por encima de 100).
{
"generated_at": "2026-10-02T12:00:00Z",
"items": [
{
"id": "!a1b2c3d4",
"short": "CAD1",
"long": "Repetidor Sierra Cádiz",
"role": "ROUTER",
"province": "ES-CA",
"battery": {"level": 34, "voltage": 3.71, "powered": false, "at": "2026-10-02T11:40:00Z"},
"chutil": 22.5,
"tx": 3.1,
"last_seen": "2026-10-02T11:58:00Z"
}
]
}
Datos de ejemplo.
Alertas · GET /alerts, GET /alerts/{id} y GET /alerts/catalog
Parámetro de /alerts |
Valores |
|---|---|
state |
open (por defecto), resolved o all |
risk |
bajo, medio, alto |
type |
infraestructura, clientes |
province |
Código de provincia |
node |
Id del nodo (!a1b2c3d4) |
since |
Fecha ISO 8601 |
limit |
Hasta 500 |
cursor |
Para pedir la página siguiente |
Los campos de las alertas van en español:
| Campo | Qué es |
|---|---|
id |
Identificador de la alerta |
regla |
Regla que la ha disparado (reboot-loop, battery-low…) |
riesgo |
bajo, medio o alto |
tipo |
infraestructura o clientes |
mensaje |
Texto legible |
nodo |
Id del nodo afectado, o all si son muchos |
nodos |
Lista de nodos cuando nodo es all |
nodo_info |
Nombre corto y largo, rol y provincia |
datos |
Valores que la dispararon (cambian según la regla) |
estado |
abierta o resuelta |
abierta_en, actualizada_en |
Fechas |
/alerts/{id} añade el historial de cambios de la alerta. /alerts/catalog devuelve los riesgos y tipos vigentes y las reglas activas con su descripción pública; si aparece un riesgo o un tipo nuevo, sale ahí.
{
"id": "01JABCDXYZ7Q8R9S0T1U2V3W4X",
"regla": "reboot-loop",
"riesgo": "alto",
"tipo": "infraestructura",
"mensaje": "CAD1 se ha reiniciado 7 veces en la última hora",
"nodo": "!a1b2c3d4",
"nodos": [],
"nodo_info": {"corto": "CAD1", "largo": "Repetidor Sierra Cádiz", "rol": "ROUTER", "provincia": "ES-CA"},
"datos": {"reinicios": 7, "ventana_min": 60, "uptime_minimo_s": 84},
"estado": "abierta",
"abierta_en": "2026-10-01T15:00:00Z",
"actualizada_en": "2026-10-01T15:42:00Z"
}
Datos de ejemplo.
Nodos en peligro · GET /nodes/at-risk
Un elemento por nodo con alertas abiertas, ordenados por riesgo máximo y antigüedad, con el motivo de cada alerta y su alert_id.
| Parámetro | Valores |
|---|---|
province |
Código de provincia |
min_risk |
Riesgo mínimo; medio por defecto |
type |
infraestructura o clientes |
{
"generated_at": "2026-10-02T12:00:00Z",
"items": [
{
"node": {"id": "!a1b2c3d4", "short": "CAD1", "long": "Repetidor Sierra Cádiz", "role": "ROUTER", "province": "ES-CA"},
"risk": "alto",
"type": "infraestructura",
"since": "2026-10-01T15:00:00Z",
"reasons": [
{"rule": "reboot-loop", "risk": "alto", "text": "7 reinicios en la última hora", "alert_id": "01JABCDXYZ7Q8R9S0T1U2V3W4X"},
{"rule": "battery-low", "risk": "medio", "text": "Batería al 34 %", "alert_id": "01JABCE0000000000000000000"}
]
}
]
}
Datos de ejemplo.
Códigos de respuesta
| Código | Cuándo |
|---|---|
200 |
Todo bien (con stale: true si son datos de la última copia guardada) |
400 |
Parámetro no válido (por ejemplo, window=3d) |
404 |
No existe (una alerta o un ranking) |
429 |
Has superado las 60 peticiones por minuto |
503 |
La fuente no responde y no hay copia guardada |
Webhooks
Si quieres las alertas en tu propio sistema en el momento en que ocurren, te las enviamos por webhook: una petición POST con JSON a tu URL cada vez que una alerta que pase tus filtros se abre, cambia o se resuelve.
Cómo darte de alta
Escribe a [email protected] con:
- La URL que recibirá los avisos (tiene que ser
https://). - Los filtros que quieras: riesgos, tipos, provincias o nodos concretos.
- Un nombre para identificar tu integración.
Con el alta recibirás un secreto propio para comprobar la firma de cada envío.
Qué recibes
| Elemento | Valor |
|---|---|
| Método | POST, cuerpo JSON |
| Cuerpo | El cambio completo de la alerta: la alerta, con los mismos campos que en /alerts, y qué ha pasado (abierta, actualizada o resuelta) |
X-SNM-Transicion |
Id del cambio. Úsalo para descartar duplicados |
X-SNM-Firma |
sha256= seguido del HMAC-SHA256 del cuerpo calculado con tu secreto |
Cómo comprobar la firma
- Lee el cuerpo tal y como llega, sin volver a formatearlo.
- Calcula su HMAC-SHA256 con tu secreto.
- Compáralo con lo que va detrás de
sha256=enX-SNM-Firma, con una comparación de tiempo constante. - Si no coincide, descarta la petición.
Reintentos
- Responde con un código
2xxpara confirmar que lo has recibido. - Si falla, lo reintentamos 3 veces: a los 10 segundos, al minuto y a los 5 minutos.
- Tras 20 fallos seguidos, tu destino se desactiva. Escríbenos para reactivarlo.
- Puede llegarte un mismo cambio dos veces si tu servidor tarda en contestar: usa
X-SNM-Transicionpara ignorar los repetidos.
Requisitos de tu URL
Solo https://. No se admiten direcciones privadas, de red local ni del propio servidor.
Chat en directo (WebSocket)
Para seguir en directo los mensajes de los canales de chat de la lista, sin pedir nada cada pocos segundos.
- Dirección:
wss://mesh.desdechipiona.es/ws/chat. Sin registro ni clave. Solo lectura: no se puede enviar nada a la malla. - Al conectar recibes la lista de canales admitidos. Suscríbete con
{"type": "subscribe", "channels": ["Cadiz", "sos"]}(o conecta con?channels=Cadiz,sos) y date de baja con"unsubscribe". - Al suscribirte recibes los últimos mensajes de ese canal y después cada mensaje nuevo con canal, remitente (id y nombres), texto y hora.
- Solo mensajes de difusión de los canales de la lista: nunca mensajes directos.
- Límites: 10 conexiones por IP. Si no lees los mensajes a tiempo, se cierra la conexión: vuelve a conectar.
- Formato completo y códigos de error: ejemplos de
../../chat-ws/README.md§4.
Uso responsable
- Respeta el límite y guarda las respuestas: los datos cambian como mucho cada 30 o 60 segundos.
- No intentes reconstruir la ubicación de nadie a partir de los datos.
- Son datos orientativos: pueden estar incompletos o tener errores.
- La versión va en la ruta (
/v1). - Si haces algo con estos datos, nos encantará saberlo: [email protected].