La instalación, paso a paso
Cada fragmento de esta página está listo para pegar. Las cifras — cuotas, límites, valores por defecto, nombres de campo — se leen del código en ejecución: esta página y el servidor dicen lo mismo.
Para empezar
Obtén tu clave
Todo aquí excepto los feeds RSS utiliza una clave API. Abre tu cuenta, sección API para desarrolladores, y cópiala. El plan gratuito no necesita tarjeta.
La clave lleva tu plan, y tu plan lleva tus volúmenes diarios. Dos contadores funcionan uno al lado del otro: uno para la API REST, uno para el servidor MCP. Un editor que hace algunas preguntas nunca consume tu presupuesto de API.
¿Qué necesito?
| Quieres… | Usar |
|---|---|
| Hacer preguntas desde tu editor | Servidor MCP |
| Consultar los datos desde tu propio código | REST API |
| Ser informado cuando algo cambie | Webhooks |
| Conectarlo a n8n, Zapier o Make | Automatizaciones |
| Verificar dependencias en cada solicitud de extracción | GitHub Action |
| Seguir en un lector, sin clave | Feeds RSS |
Servidor MCP
El servidor MCP, en breve
MCP (Protocolo de Contexto del Modelo) es una forma estándar para que un asistente de IA llame a un servicio externo. Pegas una dirección en tu cliente, y el asistente obtiene cinco herramientas que puede usar mientras trabajas. Esas herramientas consultan el catálogo de olud.ai: proyectos de IA de código abierto, modelos de IA con sus precios y alternativas de código abierto a productos comerciales.
| Hecho | Valor |
|---|---|
| Punto final | https://olud.ai/mcp.php |
| Método | POST, JSON-RPC 2.0. GET devuelve 405 (ver abajo) |
| Transporte | HTTP transmitible, un endpoint, sin flujo SSE |
| Versión del protocolo | 2025-06-18. Si el cliente solicita 2025-03-26 o 2024-11-05, el servidor responde en esa versión |
| Versión del servidor | 1.0.0 |
| Sesión | Ninguna. Sin Mcp-Session-Id que mantener, nada que expirar, nada que reconectar |
| Capacidades | solo herramientas. resources/list, resources/templates/list y prompts/list responden con listas vacías en lugar de un error |
| Autenticación | Encabezado de solicitud X-Api-Key. Opcional |
| Agrupamiento | No soportado. Un array JSON-RPC es rechazado con HTTP 400 |
El servidor lee. Lo único que escribe es tu contador de llamadas diario.
Instalarlo, cliente por cliente
La misma URL para cada cliente: https://olud.ai/mcp.php. La clave viaja en el encabezado X-Api-Key. Sin una clave, el servidor aún responde, con una menor asignación (25 llamadas al día por IP, 5 resultados por llamada). Tu clave y un bloque listo para pegar están en Tu cuenta, sección servidor MCP.
Claude Code, un comando:
claude mcp add --transport http opensourceai https://olud.ai/mcp.php \ --header "X-Api-Key: your-key" # sin una clave: claude mcp add --transport http opensourceai https://olud.ai/mcp.php # verifica lo que se registró: claude mcp list
Claude Desktop, claude_desktop_config.json:
{
"mcpServers": {
"opensourceai": {
"type": "http",
"url": "https://olud.ai/mcp.php",
"headers": { "X-Api-Key": "tu-clave" }
}
}
}Ese archivo se encuentra en ~/Library/Application Support/Claude/claude_desktop_config.json en macOS, y %APPDATA%\Claude\claude_desktop_config.json en Windows. Cierra y vuelve a abrir Claude Desktop después de editarlo; lee el archivo al iniciar.
Si Claude Desktop muestra el servidor como fallido o no lo lista en absoluto, tu compilación solo acepta servidores locales (stdio). Conéctalo con mcp-remote, que necesita Node instalado:
{
"mcpServers": {
"opensourceai": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://olud.ai/mcp.php",
"--header", "X-Api-Key:${OSAI_KEY}"],
"env": { "OSAI_KEY": "your-key" }
}
}
}Cursor, .cursor/mcp.json en el proyecto (o ~/.cursor/mcp.json para cada proyecto):
{
"mcpServers": {
"opensourceai": {
"url": "https://olud.ai/mcp.php",
"headers": { "X-Api-Key": "your-key" }
}
}
}VS Code, .vscode/mcp.json en el espacio de trabajo:
{
"servers": {
"opensourceai": {
"type": "http",
"url": "https://olud.ai/mcp.php",
"headers": { "X-Api-Key": "${input:osaiKey}" }
}
},
"inputs": [
{ "id": "osaiKey", "type": "promptString",
"description": "clave API de olud.ai", "password": true }
]
}Una clave en la URL como ?key=your-key también funciona, porque el servidor la lee. Prefiere el encabezado: las cadenas de consulta terminan en el historial del navegador, registros de proxy e historial de shell.
Comprobar que funciona, con curl
Cuando un cliente dice que un servidor no está disponible, prueba primero el servidor en sí. Esta llamada lista las herramientas y no cuesta nada: solo tools/call decrementa tu cuota, así que initialize, tools/list y ping son gratuitos. Reiniciar tu editor nunca consume el día.
curl -s -X POST https://olud.ai/mcp.php \
-H "Content-Type: application/json" \
-H "X-Api-Key: your-key" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Una respuesta saludable es HTTP 200 con {"jsonrpc":"2.0","id":1,"result":{"tools":[ ... ]}} y los cinco nombres dentro. A continuación, gasta una llamada:
curl -s -X POST https://olud.ai/mcp.php \
-H "Content-Type: application/json" \
-H "X-Api-Key: your-key" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"search_open_source_ai",
"arguments":{"query":"text to speech","limit":5}}}'Una llamada tools/call exitosa lleva dos encabezados de respuesta que vale la pena leer: X-RateLimit-Limit es tu asignación diaria, X-RateLimit-Remaining es lo que queda después de esta llamada. Se establecen solo en tools/call.
Chequeo de disponibilidad más corto posible. Responde {"jsonrpc":"2.0","id":0,"result":{}} y no necesita clave:
curl -s -X POST https://olud.ai/mcp.php \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":0,"method":"ping"}'Las cinco herramientas
No llamas a estos por nombre. Haces una pregunta, el cliente elige la herramienta. Los nombres importan cuando lees un registro o escribes la llamada tú mismo, y se emparejan exactamente: search_open_source_ai funciona, Search_Open_Source_AI no.
| Herramienta | Parámetros | Límites y valores predeterminados | Devuelve |
|---|---|---|---|
| search_open_source_ai | query (cadena, requerida), limit (entero) | limit restringido a 1 … tu techo de resultados por llamada. Predeterminado 10, o tu techo si es más bajo | matches (total encontrado, no truncado), luego id, nombre, resumen, estrellas, lenguaje, licencia, github, página, puntaje de salud y etiqueta, tendencia reciente |
| get_project | project (cadena, requerida) | Acepta un slug (ollama-ollama), owner/repo, una URL completa de github.com, o el nombre exacto del proyecto | Todo lo anterior más owner, forks, creado, empujado, temas, el desglose completo de salud, estrellas ganadas, y hasta 5 lanzamientos recientes |
| find_alternatives | product (cadena, requerida) | Nombre del producto comercial. Coincide con su slug, luego con su nombre exacto | product, página, y la lista de alternativas, limitada por tu techo de resultados por llamada |
| list_models | proveedor (cadena), max_price_out (número), min_context (entero), free_only (booleano), limit (entero) | el proveedor es una subcadena que no distingue entre mayúsculas y minúsculas. max_price_out es dólares por millón de tokens de salida. min_context está en tokens. el límite se restringe a 1 … tu límite superior, por defecto 15 o tu límite superior si es menor | coincidencias, luego id, nombre, proveedor, contexto, price_per_million (entrada y salida), modalidad, tool_calling y la URL del leaderboard |
| proyectos_en_tendencia | tipo (cadena), limit (entero) | el tipo es en tendencia, emergente o top_health. Cualquier otra cosa vuelve a en tendencia sin un error. Por defecto en tendencia. el límite se restringe a 1 … tu límite superior, por defecto 10 o tu límite superior si es menor | tipo, las tarjetas de proyecto y el tiempo de generación del índice |
Preguntas que llegan a cada herramienta
- search_open_source_ai — "Encuéntrame una biblioteca OCR de código abierto que pueda ejecutar localmente."
- get_project — "¿Qué tan saludable está ollama/ollama en este momento, y cuándo fue la última vez que se lanzó?"
- find_alternatives — "¿Qué herramienta de código abierto podría reemplazar Notion para nosotros?"
- list_models — "¿Qué modelos tienen al menos 128k de contexto y cuestan menos de $1 por millón de tokens de salida?"
- trending_projects — "¿Qué proyectos de IA de código abierto están ganando estrellas más rápido esta semana?" Pregunta por emergentes para obtener proyectos saludables que aún son poco conocidos, o top_health para los mejor mantenidos.
Los filtros eliminan lo que no pueden juzgar. Con max_price_out establecido, un modelo cuyo precio de salida no tenemos figura se deja fuera en lugar de adivinar. Con min_context establecido, un modelo sin ventana de contexto registrada cuenta como cero y se deja fuera. free_only mantiene modelos cuyo nivel es exactamente gratuito.
Un límite por encima de tu límite superior no es un error: se restringe silenciosamente. El esquema publicado dice máximo 100 porque esa es la más alta que permite cualquier plan, no lo que tu clave permite. Un límite que no es un número vuelve al valor por defecto.
Volúmenes por plan
| Sin clave | Gratis | Desarrollador | Pro | Org | |
|---|---|---|---|---|---|
| Llamadas por día | 25 | 200 | 2,000 | 20,000 | 200,000 |
| Resultados por llamada | 5 | 10 | 25 | 50 | 100 |
Dos límites, porque detienen dos cosas diferentes: las llamadas por día limitan la carga, los resultados por llamada limitan cuánto del catálogo sale en una respuesta.
- Las llamadas MCP se cuentan aparte de las llamadas a la API REST. Misma clave, dos contadores. Un editor que hace algunas preguntas nunca toca tu presupuesto de API.
- Solo se cuenta tools/call. initialize, tools/list y ping no cuestan nada.
- Ambos contadores se reinician a las 00:00 UTC.
- Sin una clave, el conteo es por dirección IP. Todos detrás de una IP de oficina comparten los 25.
- En Free y sin clave, cada respuesta termina con una línea de nota que indica tus dos límites. Las respuestas de Dev, Pro y Org no llevan tal línea.
- Org es el plan llamado negocio dentro del sistema. Los mensajes de error imprimen el nombre interno.
Cuando no funciona
Clave escrita incorrectamente, o revocada. Cada pregunta falla con:
Esa clave de API es desconocida o ha sido revocada. Elimínala para usar el nivel gratuito, o consigue una nueva en https://olud.ai/account.html
Una clave mala no vuelve silenciosamente a la asignación anónima. Corrige la clave o elimina el encabezado X-Api-Key por completo, luego reinicia el cliente para que vuelva a leer la configuración.
Límite diario alcanzado. Con una clave, luego sin:
Cuota diaria de MCP alcanzada (200 llamadas/día en el plan "gratuito"). Se reinicia a las 00:00 UTC. Niveles superiores: https://olud.ai/plans.html Límite anónimo alcanzado (25 llamadas/día por IP). Se reinicia a las 00:00 UTC. Una clave de API gratuita lo eleva a 200/día — https://olud.ai/account.html
Nada está roto y nada se cobra. Espera hasta las 00:00 UTC, o sube de nivel. Si no tienes clave, una clave gratuita te lleva de 25 a 200 llamadas y de 5 a 10 resultados por llamada.
El nombre de la herramienta no es uno de los cinco:
Herramienta desconocida "list_projects". Llama a tools/list para ver qué está disponible.
Falta un argumento requerido. Cada mensaje muestra la forma que quiere:
Falta "query". Ejemplo: {"query": "texto a voz"}
Falta "project". Ejemplo: {"project": "ollama/ollama"}
Falta "product". Ejemplo: {"product": "Midjourney"}Nada encontrado. Ambos mensajes te dicen a dónde ir a continuación:
No se encontró ningún proyecto para "ollamaa". Intenta search_open_source_ai primero para obtener su id exacto. Aún no rastreamos "photoshop". Coincidencias más cercanas: <hasta 8 nombres>. Lista completa: https://olud.ai/alternatives-hub.html
El catálogo está en medio de una reconstrucción. Espera un minuto y pregunta de nuevo; estas son las cuatro redacciones, una por conjunto de datos:
El catálogo está siendo reconstruido. Intenta de nuevo en un minuto. Esa lista está siendo reconstruida. Intenta de nuevo en un minuto. El catálogo de modelos no está disponible en este momento. El índice de alternativas no está disponible en este momento.
Abrir https://olud.ai/mcp.php en un navegador devuelve HTTP 405. Esa es la respuesta correcta: el endpoint habla JSON-RPC sobre POST, y la especificación pide 405 cuando un servidor no ofrece un flujo GET. El cuerpo te dice qué hacer y lleva la configuración para copiar. Llega en una línea; aquí está espaciado para ser leído.
{
"server": "olud.ai MCP",
"version": "1.0.0",
"protocol": "2025-06-18",
"usage": "Este endpoint habla MCP a través de JSON-RPC 2.0. Envíe una solicitud POST desde un cliente MCP.",
"config": { "mcpServers": { "opensourceai": { "url": "https://olud.ai/mcp.php" } } },
"tools": ["search_open_source_ai", "get_project", "find_alternatives",
"list_models", "trending_projects"],
"docs": "https://olud.ai/api.html"
}Rechazos a nivel de transporte. Estos cuatro llevan un código de error JSON-RPC, y los primeros tres también llevan un código de error HTTP:
405 -32600 Use POST con un cuerpo JSON-RPC 2.0. (PUT, DELETE, HEAD…) 400 -32700 Error de análisis: el cuerpo no es un JSON válido. 400 -32600 El agrupamiento JSON-RPC no es compatible (eliminado en MCP 2025-06-18). 200 -32601 Método desconocido "tools/execute". 200 -32602 Falta el nombre de la herramienta en los parámetros.
Si su cliente se queja de que falta un id de sesión, ignórelo y verifique el tipo de transporte en su configuración. Este servidor no mantiene sesiones, y no hay un Mcp-Session-Id para enviar de vuelta. Un cliente configurado para stdio o para SSE contra esta URL fallará antes de la primera llamada; el tipo es http.
REST API
Autenticación y claves
URL base: https://olud.ai/api/v1. Cada endpoint es un GET. El encabezado CORS publicita POST, pero v1 no tiene ruta POST: los parámetros siempre se leen de la cadena de consulta.
Envíe su clave en el encabezado X-Api-Key. Un parámetro de consulta ?key= también funciona, para una pestaña del navegador o una prueba rápida. Si ambos están presentes, el encabezado tiene prioridad. Solo /meta funciona sin una clave.
Ambas formas son aceptadas:
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ "https://olud.ai/api/v1/projects?limit=3" curl -s "https://olud.ai/api/v1/projects?limit=3&key=osk_YOUR_KEY"
Para obtener una clave: inicie sesión en olud.ai y abra su página de cuenta, sección API de desarrollador. La clave se crea en su primera visita, en el plan gratuito, y tiene el formato osk_ seguido de 40 caracteres hexadecimales. Rotarla desde la misma página elimina la clave antigua de inmediato: el valor antiguo devuelve 403 invalid_key.
| Encabezado | Valor | Enviado en |
|---|---|---|
| X-RateLimit-Limit | Su límite diario, como un entero | Cada solicitud que pasó la verificación de clave |
| X-RateLimit-Remaining | Solicitudes restantes hoy, redondeadas a 0 | Cada solicitud que pasó la verificación de clave, incluyendo el 429 |
| ETag | MD5 citado del tiempo de construcción del gráfico de proyectos | Cada endpoint excepto /meta |
| Access-Control-Allow-Origin | * | Cada respuesta, incluyendo OPTIONS (que responde 204) |
Sobre de respuesta
Un éxito siempre es HTTP 200 con tres claves de nivel superior: ok es true, data contiene la carga útil, meta contiene contexto. data es un objeto en endpoints de un solo registro y un array en endpoints de lista.
GET /api/v1/meta, tal cual:
{"ok":true,"data":{"name":"olud.ai API","version":"v1","generated":"2026-07-27T07:45:15+00:00","counts":{"projects":10142},"endpoints":["/project/{id}","/projects","/emerging","/alternatives/{id}","/search?q=","/models"],"docs":"https://olud.ai/api/"},"meta":{"attribution":"Puntuaciones e índices calculados por olud.ai (olud.ai). Hechos contextuales de fuentes públicas: GitHub, Hugging Face, OpenRouter, Artificial Analysis, PyPI, NPM, Docker Hub."}}meta.attribution se establece en cada respuesta de éxito, en cada endpoint, y no se puede desactivar. El resto de meta varía según el endpoint: generated, total, limit, offset, sort, freshness, points. Lea las secciones del endpoint para saber qué campos obtiene.
Un error lleva ok false, un estado HTTP diferente de 200, y error.code más error.message. No hay un objeto meta en un error, por lo que no hay campo de atribución. Pruebe ok antes de leer data — no asuma la forma de éxito.
GET /api/v1/projects sin clave, tal cual:
{"ok":false,"error":{"code":"missing_key","message":"Proporcione su clave API a través del encabezado X-Api-Key (o ?key=). Obtenga una en https://olud.ai/api/"}}Envía el ETag de vuelta como If-None-Match y obtendrás 304 con un cuerpo vacío cuando el gráfico no se haya reconstruido. El gráfico se reconstruye una vez cada mañana, por lo que las consultas más frecuentes que eso devuelven 304 todo el día.
Endpoints: meta y proyectos
GET /meta
Estado del gráfico. El único endpoint sin clave, y el único que no cuenta contra su cuota. Devuelve el nombre y la versión de la API, generado (marca de tiempo UTC de la última construcción del gráfico), counts.projects, una lista de endpoints y la URL de la documentación. Sin parámetros. Si los archivos del gráfico son ilegibles, counts es nulo y la llamada aún devuelve 200 — úselo como una verificación de salud y para decidir si vale la pena volver a obtenerlo. No envía ETag ni encabezados de límite de tasa.
curl -s https://olud.ai/api/v1/meta
GET /project/{id}
Un proyecto, registro completo. Esta es la vista combinada: hechos de GitHub, nuestra puntuación de mantenimiento, velocidad de estrellas, lanzamientos, pulso de adopción.
- Identidad: id, nombre, propietario, url, página (ruta de la página del proyecto en el sitio), desc.
- Hechos de GitHub a partir de la última construcción: estrellas, bifurcaciones, lang, licencia, temas, creado, enviado.
- salud: puntuación de 100, etiqueta, por qué, más los cuatro componentes de los que se compone: actividad (máx 30), impulso (máx 20), comunidad (máx 30), mantenimiento (máx 20) — pesos v2 desde el 29 de julio de 2026 — y verificado. Las etiquetas siguen la puntuación: 85+ Prosperando, 70+ Saludable, 50+ Mantenido, 30+ Disminuyendo, por debajo de 30 En riesgo. Un proyecto sin commits en cuatro semanas tiene su puntuación limitada a 45, por lo que los componentes pueden sumar más que la puntuación. No todos los proyectos tienen puntuación; prueba para el campo.
- velocidad: stars_1d y stars_7d, de nuestro propio historial diario de estrellas.
- lanzamientos: etiqueta, fecha, nivel, url — el más reciente primero.
- herramienta: slug, cat, pulse, docker_pulls, npm_month, pip_month, hn_hits, bsky_week, compare_pages. Presentado solo para proyectos que coinciden con una herramienta rastreada.
- rango, tendencia (estable, tranquila o acelerando) y señales (stars_accel, has_page).
| Parámetro | Tipo | Predeterminado | Comportamiento |
|---|---|---|---|
| id | segmento de ruta, requerido | ninguno | En minúsculas. Resuelto en tres pasos: slug exacto, luego el índice owner/name, luego owner-name. Ningún segmento devuelve 400 missing_id; ninguna coincidencia devuelve 404 not_found con un puntero a /search. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ https://olud.ai/api/v1/project/ollama-ollama
GET /projects
Filtrar, ordenar y paginar el catálogo. Los filtros se aplican primero, luego la ordenación, luego el desplazamiento y el límite.
| Parámetro | Tipo / límites | Predeterminado | Comportamiento |
|---|---|---|---|
| lang | cadena | sin filtro | Coincidencia exacta en el idioma del proyecto, sin distinción entre mayúsculas y minúsculas. lang=rust mantiene solo Rust. |
| licencia | cadena | sin filtro | Coincidencia de subcadena en el id de la licencia, sin distinción entre mayúsculas y minúsculas. license=gpl mantiene GPL-2.0 y AGPL-3.0. |
| vertical | cadena | sin filtro | Coincidencia exacta, sin distinción entre mayúsculas y minúsculas. Valores presentes en el gráfico: robótica, seguridad, finanzas, ciencia, atención médica, educación, legal. La mayoría de los proyectos no tienen ninguno, y todos desaparecen cuando configuras esto. |
| health_min | entero | sin filtro | Mantiene proyectos cuya health.score es mayor o igual al valor. Los proyectos sin puntuación cuentan como 0 y quedan fuera. Un valor no numérico se convierte en 0, lo que no filtra nada. |
| ordenar | stars, health, momentum o reciente | stars | Siempre descendente. momentum lee velocity.stars_7d, reciente lee la última fecha de push. Un valor desconocido se ordena por estrellas pero se devuelve tal cual en meta.sort — verifica meta.sort si el orden te sorprende. |
| límite | entero 1-100 | 25 | Los valores fuera de rango se limitan a los límites, los no numéricos vuelven a 25. No se genera un error, así que limit=500 te da silenciosamente 100. |
| desplazamiento | entero 0-100000 | 0 | Limitado de la misma manera. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ "https://olud.ai/api/v1/projects?lang=python&license=apache&sort=health&health_min=70&limit=10"
data es un array de tarjetas compactas: id, nombre, url, desc, stars, lang, license, health (solo la puntuación), momentum (stars_7d), tendencia, vertical. Los campos sin valor están presentes y son nulos. Para los componentes, lanzamientos y cifras de adopción, llama a /project/{id}. meta da total (coincidencias después de filtrar, antes de paginar), límite, desplazamiento, orden y generado.
Endpoints: búsqueda, emergentes, alternativas
GET /search
Busca proyectos por nombre, descripción y temas. No busca modelos ni filas de Hugging Face; usa /models y /hf para eso.
| Parámetro | Tipo / límites | Predeterminado | Comportamiento |
|---|---|---|---|
| q | cadena, requerida | ninguno | Recortada y en minúsculas. Vacío o solo espacios en blanco devuelve 400 missing_query. |
| límite | entero 1-50 | 15 | Limitado a los límites; no numérico vuelve a 15. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ "https://olud.ai/api/v1/search?q=ocr&limit=5"
Puntuación, para que puedas predecir el orden: coincidencia exacta de nombre 100, nombre comienza con q 60, nombre contiene q 40, descripción contiene q 15, y +20 cuando q es igual a uno de los temas del proyecto exactamente. Los empates se rompen por estrellas. Cualquier cosa que puntúe 0 se descarta. La coincidencia es una subcadena simple: sin stemming, sin tolerancia a errores tipográficos. Las tarjetas tienen la misma forma que /projects. meta da el total (todas las coincidencias, no la página) y q como normalizado.
GET /emerging
El índice de descubrimiento diario: repos jóvenes con crecimiento sostenido y, donde podemos puntuarlos, buena salud. Clasificados por nuestro Discovery Score. Las tarjetas llevan dos campos adicionales: signals y detected, la fecha en que el proyecto ingresó por primera vez al índice (nulo cuando se desconoce).
| Parámetro | Tipo / límites | Predeterminado | Comportamiento |
|---|---|---|---|
| límite | entero 1-100 | 25 | Limitado. Puedes recibir menos filas de las que pediste: los ids que ya no están en el gráfico de proyectos se omiten. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ "https://olud.ai/api/v1/emerging?limit=10"
La frescura depende del plan, y meta dice cuál tienes. Pro y Business leen el índice de esta mañana y obtienen meta.freshness "en tiempo real". Free y Dev leen la instantánea de ayer y obtienen "del día anterior" más una meta.note. Dos consecuencias que vale la pena conocer: en Free y Dev, meta.criteria es nulo, porque el texto de criterios se almacena solo con el índice actual; y si falta el archivo de instantánea de ayer, Free y Dev reciben el índice actual mientras que meta.freshness sigue leyendo "del día anterior".
GET /alternatives/{product}
Alternativas de código abierto a un producto comercial. data devuelve nombre, dominio, desc, página, cat y un array de alternativas cuyas entradas contienen nombre, repo, sitio, licencia y desc. repo puede ser una cadena vacía cuando el proyecto no tiene repo en GitHub. meta da generado.
| Parámetro | Tipo | Predeterminado | Comportamiento |
|---|---|---|---|
| producto | segmento de ruta, requerido | ninguno | En minúsculas. Clave de producto exacta primero; si falla, el primer producto cuyo clave o nombre contenga tu cadena, en orden de archivo. Ningún segmento devuelve 400 missing_id; ninguna coincidencia devuelve 404 not_found. Pasa el slug exacto — el que está en la URL de la página /alternatives/<slug>.html — cuando necesites un producto específico, porque la coincidencia suelta toma el primer resultado, no el mejor. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ https://olud.ai/api/v1/alternatives/notion
Endpoints: modelos, hf, historia
GET /models
Catálogo de modelos construido a partir de OpenRouter. Cada fila: id, nombre, proveedor, tier, ctx (ventana de contexto en tokens), price_in y price_out (dólares estadounidenses por millón de tokens, redondeado a dos decimales, 0 cuando la fuente no informa precio), modalidad (por ejemplo texto->text o texto+imagen+archivo->text), herramientas (booleano) y rango. Los campos sin valor se eliminan de la fila, así que verifica la presencia en lugar de asumir que price_in existe. meta da total, límite, desplazamiento, generado y fuente.
| Parámetro | Tipo / límites | Predeterminado | Comportamiento |
|---|---|---|---|
| proveedor | cadena | sin filtro | Coincidencia de subcadena, sin distinción entre mayúsculas y minúsculas. provider=mistral coincide con "Mistral AI". |
| tier | gratis o de pago | sin filtro | Coincidencia exacta, sin distinción entre mayúsculas y minúsculas en tu entrada. |
| límite | entero 1-200 | 50 | Limitado a los límites; no numérico vuelve a 50. |
| desplazamiento | entero 0-100000 | 0 | Aplicado a la lista combinada. Las filas de peso abierto vienen primero, luego las propietarias, cada bloque en orden de rango. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ "https://olud.ai/api/v1/models?tier=free&provider=mistral&limit=20"
GET /hf
Hugging Face: más descargado y en tendencia. Sin parámetros. data tiene tres arrays — top_llm (modelos de generación de texto por descargas en 30 días), top (todas las tareas, limitado a 30 filas por este endpoint) y trending (los que más suben hoy). Cada fila: id, org, nombre, tarea, descargas (últimos 30 días), me gusta, licencia, restringido, creado, actualizado, tendencia (aumento en me gusta en filas en tendencia, nulo en otros lugares). meta da generado y fuente.
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ https://olud.ai/api/v1/hf
GET /history/{slug}
Serie diaria para un proyecto: estrellas, salud y velocidad de 7 días. Solo claves Pro y Business — el plan Business es el que se vende como Org. Cualquier otro plan recibe 403 pro_required. data devuelve slug, días (fechas como AAAA-MM-DD, el más antiguo primero) y series con tres arrays, estrellas, salud y velocidad_7d, alineados índice por índice con días. Las entradas individuales pueden ser nulas cuando faltaba el valor de un día. meta da puntos y ventana.
| Parámetro | Tipo | Predeterminado | Comportamiento |
|---|---|---|---|
| slug | segmento de ruta, requerido | ninguno | En minúsculas, y debe coincidir con ^[a-z0-9][a-z0-9._-]*$ — el mismo id que /project/{id}. Cualquier otra cosa devuelve 400 bad_slug. Un slug válido sin nada registrado aún devuelve 404 not_found. |
curl -s -H "X-Api-Key: osk_YOUR_KEY" \ https://olud.ai/api/v1/history/ollama-ollama
Cuotas diarias
Las solicitudes se cuentan por clave, por día UTC. El contador se reinicia a las 00:00 UTC. No hay límite por segundo o por minuto en el código.
| Plan en la clave | Solicitudes / día | Nombre público | Qué cambia |
|---|---|---|---|
| gratis | 500 | Gratis | Todo excepto /history. /emerging sirve el índice del día anterior. |
| desarrollo | 5000 | Desarrollador | Mismo acceso que Gratis, límite más alto. |
| pro | 50000 | Pro | /history se abre. /emerging sirve el índice de esta mañana. |
| negocios | 500000 | Org | Mismo acceso que Pro, límite más alto. |
Lo que cuenta: cada endpoint excepto /meta, una unidad por solicitud. El contador se incrementa justo después de que se verifica la clave y antes de cualquier otra cosa, por lo que un 304, un 404 not_found, un 400 missing_query y un 503 graph_unavailable cuestan una unidad. Solo 401 missing_key y 403 invalid_key no cuestan nada, ya que no hay una clave válida que cobrar.
Un campo de cuota establecido en tu clave anula el valor predeterminado del plan. X-RateLimit-Limit es la autoridad sobre tu límite, no la tabla anterior.
Pasado el límite, cada solicitud devuelve 429 quota_exceeded hasta el reinicio. La solicitud rechazada no se cuenta, nada se pone en cola y nada se cobra. No se envía un encabezado Retry-After: el reinicio es a las 00:00 UTC.
Cómo se ve una solicitud rechazada en una clave gratuita:
HTTP/2 429
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
{"ok":false,"error":{"code":"quota_exceeded","message":"Cuota diaria alcanzada (500 solicitudes/día en el plan \"gratis\"). Se reinicia a las 00:00 UTC."}}Códigos de error
Cada error devuelve error.code como una cadena estable. Rama en eso, no en el texto del mensaje, que contiene slugs y nombres de planes y cambia con la solicitud.
| HTTP | código | Disparador | Qué hacer |
|---|---|---|---|
| 401 | missing_key | Sin encabezado X-Api-Key y sin ?key=. También lo que obtienes para una ruta desconocida cuando no se envía clave, porque la verificación de clave se ejecuta antes del enrutamiento. | Envía la clave. Un 401 en una ruta que estás seguro que existe es un encabezado faltante, no una ruta incorrecta. |
| 403 | invalid_key | La clave no está en el almacén, o su bandera activa es falsa. Rotar tu clave elimina la anterior. | Lee la clave actual desde tu página de cuenta y actualiza al llamador. La rotación rompe cada copia desplegada de la clave antigua a la vez. |
| 403 | pro_required | /history llamado con una clave gratuita o de desarrollo. | Lee los valores de hoy desde /project/{id} (salud, velocidad), o pasa a Pro. |
| 429 | quota_exceeded | El contador diario alcanzó tu límite. | Deja de llamar hasta las 00:00 UTC, o aumenta el plan. Lee X-RateLimit-Limit para confirmar tu límite real. |
| 400 | missing_id | /project o /alternatives llamado sin segmento de ruta. | Agrega el segmento. /v1/project solo no es un listado — usa /v1/projects. |
| 400 | consulta_faltante | /search con q vacío o solo espacios en blanco. | Envía un q no vacío. Ten en cuenta que la solicitud aún fue contabilizada. |
| 400 | slug_incorrecto | /history slug vacío, o que contiene un carácter fuera de ^[a-z0-9][a-z0-9._-]*$. | Pasa el id exactamente como fue devuelto por /projects o /search. |
| 404 | no_encontrado | ID de proyecto desconocido, no hay alternativas rastreadas para ese producto, o no hay historial almacenado para ese slug. | Para un proyecto, vuelve a intentar a través de /search?q=. Para el historial, el proyecto puede haber entrado en seguimiento demasiado recientemente para tener puntos. |
| 404 | punto_final_desconocido | Clave válida, ruta no en el enrutador. | Verifica la ortografía. /meta lista seis puntos finales y omite /hf y /history, que ambos existen. |
| 503 | grafico_no_disponible | Falta un archivo gráfico o es ilegible, lo que ocurre mientras la construcción de la mañana lo escribe. | Vuelve a intentar en un minuto. Tu clave está bien; no la gires. |
| 503 | no_listo | /hf antes de que el escaneo matutino de Hugging Face haya producido su archivo. | Vuelve a intentar más tarde en el día. Otros puntos finales no se ven afectados. |
| 304 | sin cuerpo | If-None-Match coincidió con el ETag actual. | Sirve tu copia en caché. Recuerda que consumió una solicitud de tu cuota. |
Política de reintento que coincide con el código: reintentar en 503 después de un minuto, y en 429 solo después del reinicio UTC. Nunca reintentes un 400, 403 o 404 sin cambios — la respuesta no cambiará y cada intento cuesta una solicitud.
Webhooks
Creando un webhook
Un webhook es un POST firmado a tu punto final por evento. Tú eliges los eventos, los repos y la forma del cuerpo. Los webhooks necesitan un plan de pago: con una clave gratuita, la creación responde 403 paid_feature y la página de la cuenta muestra un panel bloqueado en lugar del formulario.
| Plan reportado por la API | Webhooks permitidos |
|---|---|
| gratis | 0 |
| desarrollo | 3 |
| pro | 10 |
| negocios | 50 |
Un nombre de plan que no reconocemos vuelve a 1. Una vez que llegues a tu cuenta, la creación responde 429 limit_reached — elimina uno o cambia de plan.
Desde tu cuenta
- Inicia sesión y abre /account.html. La tarjeta de Webhooks aparece una vez que tu clave API se ha cargado, y permanece oculta hasta entonces.
- Pega tu punto final en el campo de URL. La página rechaza cualquier cosa que no comience con https://.
- Marca los eventos. release y health están marcados por ti; license y new_project no lo están.
- Deja el campo de repos vacio para recibir todo, o escribe entradas owner/name separadas por comas.
- Elige el destino en la selección: tu propio punto final (JSON firmado en bruto), Slack o Discord.
- Presiona Crear. El secreto aparece una vez, en un cuadro verde. Cópialo antes de salir de la página — nada lo mostrará de nuevo.
- Cada fila de webhook lleva un botón Enviar ping de prueba y un enlace Eliminar. Eliminar detiene las entregas de inmediato.
Desde la API — crear. Tu clave API es la que está en tu cuenta, enviada como X-Api-Key (o ?key=).
curl -sS -X POST https://olud.ai/api/webhooks.php \
-H "X-Api-Key: $OSAI_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example/osai-hook",
"events": ["release", "health", "license"],
"repos": ["acme/inference-server", "acme/tiny-router"],
"format": "json"
}'El bloque de datos de la respuesta. Cada respuesta también lleva un bloque meta con nuestra línea de atribución. El secreto está aquí y en ningún otro lugar.
{
"id": "whk_9b41c7e2f0a5d3861c4e",
"url": "https://your-app.example/osai-hook",
"events": ["release", "health", "license"],
"repos": ["acme/inference-server", "acme/tiny-router"],
"secret": "whs_7d2a19f4c6b03e58a1d7f492c0b6e35847ac91d2e6f0b8a3",
"note": "Guarda este secreto ahora — se muestra solo una vez. Verifica cada entrega: X-OSAI-Signature == \"sha256=\" + HMAC_SHA256(raw_body, secret). Pruébalo: POST {\"ping\":\"whk_9b41c7e2f0a5d3861c4e\"}."
}Lo que debe satisfacer la URL
- El esquema https. http es rechazado.
- Debe haber un host presente.
- Si escribes un puerto, debe ser 443. https://your-app.example:8443/hook es rechazado.
- El host no puede ser localhost y no puede terminar en .local o .internal.
- Resolviendo el host. Si alguna dirección que devuelve está en un rango privado o reservado, la URL es rechazada.
- No llamamos a tu endpoint durante la creación. Un host que no se resuelve pasa esta verificación y falla más tarde, en la entrega. Envía un ping de prueba para averiguarlo.
- Una URL incorrecta responde 400 bad_url.
Qué repos acepta
- El valor predeterminado es ["*"] — cada repo que rastreamos.
- Las entradas se recortan y se convierten a minúsculas, por lo que la coincidencia no distingue entre mayúsculas y minúsculas.
- Una entrada debe parecerse a owner/name: owner comienza con una letra o dígito, luego letras, dígitos, punto, guion bajo, guion.
- Las entradas que no coinciden se eliminan sin más. Si nada sobrevive, obtienes 400 bad_repos.
- "*" en cualquier lugar de la lista reemplaza toda la lista. ["acme/one", "*"] se almacena como ["*"].
- Más de 100 entradas responden 400 too_many_repos. Usa "*" a partir de ese punto.
Los cuatro eventos
Cada evento lleva un repo. Un evento es un POST — los eventos nunca se agrupan. El nombre está en el encabezado X-OSAI-Event y en el campo de evento del cuerpo.
| evento | Se activa cuando | Claves dentro de los datos |
|---|---|---|
| salud | la etiqueta de salud de un repo cambia | de, a, estrellas, página |
| lanzamiento | aparece una nueva etiqueta de lanzamiento para un repo | etiqueta, nombre, url |
| licencia | la licencia que tenemos para un repo cambia | de, a |
| nuevo_proyecto | un repo entra en el directorio | estrellas, página |
salud
{
"webhook_id": "whk_9b41c7e2f0a5d3861c4e",
"event": "health",
"repo": "acme/inference-server",
"data": {
"from": "Thriving",
"to": "Slowing down",
"stars": 58120,
"page": "https://olud.ai/project/acme-inference-server.html"
},
"sent_at": "2026-07-27T05:41:12+00:00"
}de y a son etiquetas, no números. Las seis etiquetas que publicamos: Thriving, Healthy, Maintained, Slowing down, At risk, Archived. estrellas es el conteo de estrellas que tenemos para el repo. Ningún evento se activa la primera vez que un repo recibe una etiqueta — un cambio necesita un valor anterior para comparar.
lanzamiento
{
"webhook_id": "whk_9b41c7e2f0a5d3861c4e",
"event": "release",
"repo": "acme/inference-server",
"data": {
"tag": "v0.6.2",
"name": "Acme Inference Server",
"url": "https://github.com/acme/inference-server/releases"
},
"sent_at": "2026-07-27T05:41:12+00:00"
}etiqueta es la etiqueta git. nombre es el nombre de visualización del proyecto en nuestro directorio, no el título del lanzamiento. url siempre apunta a la página de lanzamientos del repo, nunca a un lanzamiento específico — construye la URL de la etiqueta tú mismo si la necesitas.
licencia
de y a son las cadenas de licencia que tenemos. Este evento no tiene clave de página.
{
"webhook_id": "whk_9b41c7e2f0a5d3861c4e",
"event": "license",
"repo": "acme/inference-server",
"data": {
"from": "Apache-2.0",
"to": "BSL-1.1"
},
"sent_at": "2026-07-27T05:41:12+00:00"
}nuevo_proyecto
{
"webhook_id": "whk_9b41c7e2f0a5d3861c4e",
"event": "new_project",
"repo": "acme/tiny-router",
"data": {
"stars": 1840,
"page": "https://olud.ai/project/acme-tiny-router.html"
},
"sent_at": "2026-07-27T05:41:12+00:00"
}Como máximo 25 eventos new_project por ejecución, con los conteos de estrellas más altos primero. En una primera ejecución, o cuando más de 200 repos aparecen a la vez, se registran y ninguno se envía — esa protección evita que una reimportación te inunde.
Cuerpo y encabezados
Con formato json, el cuerpo tiene cinco claves, en este orden. No se añade nada más.
{
"webhook_id": "whk_9b41c7e2f0a5d3861c4e",
"event": "release",
"repo": "acme/inference-server",
"data": { },
"sent_at": "2026-07-27T05:41:12+00:00"
}webhook_id es el id que creaste: whk_ más 20 caracteres hexadecimales. repo es el propietario/nombre en minúsculas, y es nulo solo en un ping de prueba. data es un objeto, vacío para un evento que no lleva campos. sent_at es UTC, ISO 8601 con un desplazamiento.
| Encabezado | Valor |
|---|---|
| Content-Type | application/json |
| User-Agent | olud.ai-Webhooks/1.0 |
| X-OSAI-Event | salud, lanzamiento, licencia, nuevo_proyecto o ping |
| X-OSAI-Delivery | 16 caracteres hexadecimales, nuevos para cada POST |
| X-OSAI-Signature | sha256= seguido del HMAC-SHA256 del cuerpo |
Esos cinco son el conjunto completo. X-OSAI-Delivery no se almacena de nuestro lado — úsalo para detectar un duplicado en tu propio registro.
Un ping de prueba, enviado por POST {"ping":"whk_…"}. Mismos encabezados, misma firma, X-OSAI-Event: ping.
{
"webhook_id": "whk_9b41c7e2f0a5d3861c4e",
"event": "ping",
"repo": null,
"data": {
"message": "Funciona. Los eventos reales llegan después de cada escaneo matutino."
},
"sent_at": "2026-07-27T05:41:12+00:00"
}Verificando la firma
Recalcula el HMAC-SHA256 del cuerpo en bruto con tu secreto, prefíjalo con sha256=, y compáralo con X-OSAI-Signature. El secreto es la cadena whs_ de la respuesta de creación: whs_ más 48 caracteres hexadecimales.
PHP
<?php
$raw = file_get_contents('php://input'); // bytes en bruto, antes de cualquier análisis
$sent = $_SERVER['HTTP_X_OSAI_SIGNATURE'] ?? '';
$expect = 'sha256=' . hash_hmac('sha256', $raw, $SECRET);
if (!hash_equals($expect, $sent)) { // tiempo constante
http_response_code(401);
exit;
}
$event = json_decode($raw, true); // analiza solo después de la verificación
http_response_code(200);Node, con Express
const crypto = require('crypto');
const express = require('express');
const app = express();
// express.raw mantiene los bytes. express.json() los destruiría.
app.post('/osai-hook', express.raw({ type: 'application/json' }), (req, res) => {
const sent = req.get('X-OSAI-Signature') || '';
const expect = 'sha256=' + crypto.createHmac('sha256', SECRET)
.update(req.body).digest('hex');
// timingSafeEqual lanza un RangeError en buffers de diferente longitud,
// así que verifica la longitud primero, luego compara en tiempo constante.
const a = Buffer.from(sent), b = Buffer.from(expect);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(200);
});Python, con Flask
import hmac, hashlib, json
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/osai-hook")
def osai_hook():
raw = request.get_data() # bytes, antes de cualquier análisis
expect = "sha256=" + hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
sent = request.headers.get("X-OSAI-Signature", "")
if not hmac.compare_digest(expect, sent): # tiempo constante
abort(401)
event = json.loads(raw)
return "", 200Compara con hash_equals, crypto.timingSafeEqual o hmac.compare_digest. Nunca con == o ===. Una comparación de cadenas simple se detiene en el primer byte que difiere, así que el tiempo que toma le dice a un atacante cuántos bytes iniciales acertaron. Repite la medición y recuperan la firma byte por byte, luego te envían eventos falsificados. Las tres funciones anteriores leen cada byte, sea cual sea la entrada.
- Hashea los bytes en bruto, antes de cualquier análisis. Un cuerpo que un marco analizó y re-serializó se convierte en algo diferente, y cada entrega parecerá inválida.
- El formato json escapa caracteres no ASCII como \uXXXX. Re-serializar también pierde eso.
- Un encabezado faltante llega como una cadena vacía. Los tres ejemplos lo rechazan en lugar de fallar.
- El secreto se muestra una vez, en la creación. Si lo pierdes, elimina el webhook y crea otro — obtienes un nuevo id y un nuevo secreto.
- Mantén un secreto por webhook. Dos webhooks nunca comparten uno.
json, slack, discord
el formato se elige en la creación, y por defecto es json. No hay un endpoint de actualización: para cambiarlo, elimina el webhook y crea otro. GET informa el formato de cada webhook; la respuesta de creación no lo repite. Un valor fuera de las tres respuestas 400 bad_format.
| formato | Lo que publicamos | URL para pegar |
|---|---|---|
| json | la carga útil anterior, sin cambios | tu propio endpoint |
| slack | texto, más un bloque de sección mrkdwn | una URL de webhook entrante de Slack |
| discord | un embed: título, url, descripción, color, pie de página | una URL de webhook de canal de Discord |
slack
Un evento de lanzamiento, publicado en Slack
{
"text": "🚀 acme/inference-server lanzó v0.6.2 — Acme Inference Server",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚀 *<https://github.com/acme/inference-server/releases|acme/inference-server lanzó v0.6.2>*\nAcme Inference Server"
}
}
]
}Obtén la URL de Slack: añade una aplicación al espacio de trabajo, activa Webhooks entrantes, añade uno para el canal que deseas, copia la URL https://hooks.slack.com/services/… y pégala en el campo de url. El texto siempre se completa — es lo que Slack muestra en la notificación y en clientes que no renderizan bloques.
discord
Un evento de licencia, publicado en Discord
{
"embeds": [
{
"title": "🔑 acme/inference-server cambió de licencia",
"url": "https://github.com/acme/inference-server",
"description": "Apache-2.0 → BSL-1.1",
"color": 14427686,
"footer": { "text": "olud.ai" }
}
]
}Obtén la URL de Discord: configuración del canal, Integraciones, Webhooks, Nuevo Webhook, Copiar URL del Webhook — https://discord.com/api/webhooks/… — y pégala en el campo de url. el color es un entero decimal: 14427686 (#DC2626) para licencia, 6514417 (#6366F1) para lanzamiento, new_project y ping.
Ambos hosts pasan la verificación de URL. Cuando un evento de licencia no lleva página, el enlace vuelve a https://github.com/owner/name; health y new_project enlazan a la página del proyecto en olud.ai; release enlaza a la página de lanzamientos del repo.
Entrega, fallo, desactivación
Las entregas dependen del pase de alerta, send-alerts.php — la misma detección que produce los correos electrónicos de los miembros. El despacho se ejecuta justo después de la detección, en el mismo proceso. No hay un horario separado ni cola. La API describe la cadencia como "diaria, después del escaneo matutino".
- Una POST por evento, por webhook coincidente. Un webhook recibe un evento cuando el nombre del evento está en su lista de eventos, y cuando el repo está en su lista de repos o repos es ["*"] .
- Un éxito es HTTP 200 a 299, dentro del tiempo de espera de 6 segundos. Un 4xx, un 5xx, un tiempo de espera, un fallo de DNS o TLS cuentan como fallos.
- Un éxito establece fails de nuevo a 0 y añade 1 a delivered.
- Un fallo añade 1 a fails. No hay reintento. El evento no se envía de nuevo — la próxima entrega es el siguiente cambio que detectamos.
- Con 10 fallos consecutivos, el webhook se desactiva, sellado con la hora UTC de ese momento, y los eventos restantes de esa ejecución se omiten para él.
- Un webhook desactivado permanece en tu lista, marcado como desactivado. No hay llamada para reactivarlo: elimínalo y crea otro, con un nuevo id y un nuevo secreto.
Leyendo el estado
curl -sS https://olud.ai/api/webhooks.php -H "X-Api-Key: $OSAI_KEY"
La respuesta, meta incluida
{
"ok": true,
"data": [
{
"id": "whk_9b41c7e2f0a5d3861c4e",
"url": "https://your-app.example/osai-hook",
"events": ["release", "health"],
"repos": ["*"],
"format": "json",
"created": "2026-07-20T09:14:02+00:00",
"delivered": 37,
"fails": 0,
"disabled": null
}
],
"meta": {
"plan": "dev",
"used": 1,
"limit": 3,
"events": ["health", "release", "license", "new_project"],
"formats": ["json", "slack", "discord"],
"delivery": "diaria, después del escaneo matutino",
"signature": "X-OSAI-Signature: sha256=HMAC_SHA256(body, secret)"
}
}delivered es el conteo de por vida de POSTs exitosos. fails es la racha actual consecutiva, por lo que vuelve a 0 en el siguiente éxito. disabled es null, o la marca de tiempo en la que nos detuvimos. Los secretos nunca son devueltos por GET.
Cuatro formas en que las entregas se detienen
- Tu endpoint sigue fallando. Diez fallos seguidos y el webhook se desactiva. Arregla el endpoint, elimina el webhook, crea uno nuevo, píngalo.
- Tu plan vuelve a ser gratuito — una cancelación. El webhook se mantiene y se omite en cada despacho, en silencio. Cambiar de plan nuevamente lo despierta, mismo id, mismo secreto.
- Rotas tu clave API en tu cuenta. La clave antigua sale del almacén de claves mientras el webhook aún apunta a ella: se cae de tu lista, ya no se puede pingear ni eliminar, y cada despacho lo omite como si fuera una clave gratuita. Elimina tus webhooks antes de rotar la clave, luego créalos nuevamente con la nueva.
- Lo eliminas. Las entregas se detienen de inmediato.
Pruebas sin esperar
curl -sS -X POST https://olud.ai/api/webhooks.php \
-H "X-Api-Key: $OSAI_KEY" -H "Content-Type: application/json" \
-d '{"ping":"whk_9b41c7e2f0a5d3861c4e"}'Ambos resultados
HTTP/1.1 200
{"ok":true,"data":{"ping":"delivered","http":200}}
HTTP/1.1 502
{"ok":false,"error":{"code":"ping_failed",
"message":"Tu endpoint respondió HTTP 500 — se espera un 2xx."}}El ping se envía en el formato del webhook, firmado de la misma manera, con evento ping y repo null. No mueve ni delivered ni fails, por lo que un ping fallido nunca te empuja hacia el umbral de desactivación. También funciona en un plan gratuito cuando el webhook ya existe, mientras que las entregas programadas no.
HTTP 0 en un mensaje ping_failed significa que no obtuvimos respuesta en absoluto: el host no se resolvió, TLS no se completó, o se agotaron los 6 segundos.
Códigos de error
Cada fallo vuelve en la misma forma, con un estado HTTP correspondiente.
{
"ok": false,
"error": {
"code": "bad_url",
"message": "La URL debe ser https, accesible públicamente, puerto estándar (sin localhost o rangos privados)."
}
}| HTTP | código | Cuando |
|---|---|---|
| 400 | bad_json | El cuerpo no es un objeto JSON. |
| 400 | bad_url | La URL falló la verificación: no es https, un puerto diferente a 443, localhost, .local, .internal, o una dirección en un rango privado o reservado. |
| 400 | bad_events | Después de filtrar, los eventos no contenían ninguno de health, release, license, new_project. |
| 400 | bad_repos | Ninguna entrada fue "*" o un propietario/nombre válido. |
| 400 | too_many_repos | Más de 100 repos en un webhook. |
| 400 | bad_format | el formato no era json, slack o discord. |
| 401 | missing_key | No hay encabezado X-Api-Key y no hay parámetro ?key=. |
| 403 | bad_key | La clave no está en nuestra tienda de claves. Una clave rotada o revocada cae aquí. |
| 403 | paid_feature | Tu plan permite 0 webhooks. |
| 404 | no_encontrado | El id en ping o en ?id= no está asociado a tu clave. |
| 405 | method_not_allowed | Un método diferente a GET, POST o DELETE. |
| 429 | limit_reached | Ya tienes tantos webhooks como permite tu plan. |
| 500 | store_write_failed | No pudimos escribir el cambio en el disco. Intenta de nuevo. |
| 502 | ping_failed | Tu endpoint respondió al ping de prueba con algo diferente a un 2xx. |
Dos puntos prácticos. Gestionar webhooks no consume tu cuota diaria de solicitudes API: este endpoint nunca toca el contador y no envía encabezados X-RateLimit. Y DELETE está ausente de la lista de métodos permitidos por CORS, por lo que una llamada de origen cruzado desde un navegador falla en la prevalidación; elimínalo del lado del servidor, o desde la página de la cuenta, que es del mismo origen.
Automatizaciones
n8n: leer y recibir
Dos nodos cubren ambas direcciones. Un nodo de Solicitud HTTP lee nuestros datos. Un nodo de Webhook recibe nuestros eventos. No hay nada que instalar de la lista de la comunidad.
Lee el catálogo
Cada endpoint responde a GET en https://olud.ai/api/v1, con tu clave en el encabezado X-Api-Key. /meta es el único endpoint que responde sin una clave y sin tocar tu cuota: úsalo como el primer nodo mientras pruebas, porque verifica la URL y la ruta de red antes de que gastes una llamada.
Recibe los eventos
Agrega un nodo de Webhook, método POST, y copia su URL de producción. Registra esa URL en tu cuenta (sección Webhooks), o envíala a https://olud.ai/api/webhooks.php con tu clave. Cuatro eventos para elegir: release, health, license, new_project. Las entregas se envían una vez al día, en el mismo pase que envía las alertas por correo electrónico: no en el segundo en que algo sucede.
Un cuerpo de entrega, con formato json (el predeterminado):
Lo que hay en data depende del evento:
- release — tag, name, url (la página de lanzamientos del repo)
- health — from, to, stars, page
- license — from, to
- new_project — stars, page
- ping — message (solo entregas de prueba)
En un evento de salud, from y to son etiquetas, no números: Thriving (puntaje 85 y más), Healthy (70+), Maintained (50+), Slowing down (30+), At risk (por debajo de 30). Compara texto en tu nodo IF, no enteros.
Cada entrega lleva tres encabezados: X-OSAI-Event, X-OSAI-Delivery (un id único por entrega, úsalo para eliminar duplicados) y X-OSAI-Signature, de la forma sha256=<HMAC-SHA256 del cuerpo con tu secreto de webhook>. El HMAC cubre los bytes exactos que enviamos, así que si quieres verificarlo, activa la opción raw-body del nodo Webhook. Un cuerpo que ha sido analizado y re-serializado nunca coincide.
Pega esto en el lienzo. Los dos nodos están intencionadamente desconectados: cada uno es su propio punto de partida:
Luego pon tu clave en el campo de encabezado del nodo HTTP, y activa el flujo de trabajo: la URL de producción de un nodo de Webhook escucha solo mientras el flujo de trabajo está activo.
Cuando no llega nada
- Registrar la URL responde 400 bad_url: el endpoint debe ser https, en el puerto 443, en un host públicamente resoluble. Un n8n autoalojado en http, en localhost, en un nombre *.local o en un rango de IP privado es rechazado.
- Registrar responde 403 paid_feature: los webhooks comienzan en el plan Dev: 3 endpoints en Dev, 10 en Pro, 50 en Org. Una clave Free no puede crear uno.
- Registrar responde 429 limit_reached: ya tienes tantos webhooks como permite tu plan. Elimina uno primero.
- No esperes hasta mañana para averiguarlo: POST {"ping":"whk_…"} a /api/webhooks.php y el evento de prueba se envía inmediatamente, en la misma forma que uno real. Si tu endpoint no responde 2xx obtienes 502 ping_failed con el código que devolvió.
- Diez respuestas no-2xx consecutivas desactivan el webhook, silenciosamente. Un éxito restablece el contador. Un webhook desactivado solo vuelve recreándolo. El tiempo de espera de entrega es de 6 segundos, así que un nodo que responde lentamente cuenta como un fallo.
- El webhook fue creado pero no se entrega nada y no hay errores: verifica el plan en la clave que lo posee. Si ha vuelto a Free, las entregas se omiten mientras el endpoint permanezca registrado.
- El secreto se muestra una vez, en la creación. No hay forma de leerlo de nuevo: elimínalo y recrea.
Zapier y Make
Las mismas dos direcciones, la misma clave, ninguna aplicación para encontrar en sus directorios.
Recibir: atrapar el gancho
En Zapier: Webhooks de Zapier, activar Catch Hook. En Make: el módulo Webhooks, Webhook personalizado. Ambos te entregan una URL https en su propio dominio, que pasa nuestra verificación. Pégala en tu cuenta como un endpoint de webhook, elige tus eventos, luego envíate un ping y confirma que el Zap o escenario lo recibe antes de construir los pasos detrás de él.
Si planeas verificar X-OSAI-Signature, necesitas el cuerpo crudo y los encabezados. En Zapier eso significa Catch Raw Hook en lugar de Catch Hook. En Make, activa la configuración de webhook que mantiene los encabezados de la solicitud. Nuestra firma es un HMAC sobre los bytes exactos del cuerpo, así que un paso que analiza el JSON antes de que lo veas hace que la comparación sea imposible.
Leer: un paso HTTP
La acción GET de Webhooks de Zapier, o el módulo HTTP de Make. Un paso, un encabezado:
Un éxito es {"ok":true,"data":[…],"meta":{…}}. Un error es {"ok":false,"error":{"code":"…","message":"…"}} con un estado HTTP correspondiente. Prueba que esté ok antes de mapear campos: un cuerpo de error no tiene datos en absoluto, y un mapeo que lee data[0] en un error produce silenciosamente valores vacíos aguas abajo.
- 401 missing_key — el encabezado no llegó. Verifica que el paso lo envíe en cada llamada, no solo en la primera.
- 403 invalid_key — clave desconocida o revocada.
- 429 quota_exceeded — cuota diaria alcanzada, se restablece a las 00:00 UTC. Las respuestas exitosas llevan X-RateLimit-Limit y X-RateLimit-Remaining, para que puedas anticiparlo.
- 503 graph_unavailable — el gráfico está siendo reconstruido. Reintenta en un minuto; este es el único error que vale la pena un reintento automático.
- 403 pro_required — /history/{slug} es solo para Pro y Org.
- 404 not_found — no existe tal proyecto o producto. El mensaje lleva una sugerencia de /search que puedes seguir.
GitHub Action: vigilancia de dependencias
La acción lee los manifiestos de dependencia del repo revisado, nos pregunta la licencia y el registro de mantenimiento de cada dependencia, y publica un comentario en la solicitud de extracción enumerando lo que necesita una decisión. Reescribe ese mismo comentario en cada ejecución — lo encuentra nuevamente a través de un marcador oculto — así que una solicitud de extracción larga no se llena de duplicados.
Todo el flujo de trabajo, con cada entrada en su valor predeterminado. Solo se requiere api-key:
actions/checkout debe venir primero: la acción lee archivos del directorio de trabajo, y solo en la raíz a menos que configures rutas.
| Entrada | Predeterminado | Qué hace |
|---|---|---|
| api-key | requerido | Enviado como X-Api-Key. Una búsqueda por cada dependencia distinta. Una clave gratuita funciona. |
| github-token | ${{ github.token }} | Publica el comentario. Necesita solicitudes de extracción: escribir en el trabajo. |
| health-floor | 45 | Marca una dependencia que puntúe por debajo de esto de 100. 0 desactiva la verificación de salud y mantiene solo las licencias. Lee el límite conocido a continuación. |
| licencias | AGPL-3.0,BSL-1.1,SSPL-1.0,Elastic-2.0,BUSL-1.1,NOASSERTION | Separadas por comas, coinciden sin distinción de mayúsculas con la licencia que poseemos. Configurarlo reemplaza la lista, no la añade. |
| fail-on-finding | false | Fallar la verificación en lugar de solo comentar. |
| paths | vacío | Manifiestos separados por comas para leer. Vacío significa: buscar los cuatro nombres conocidos en la raíz. |
NOASSERTION está en la lista predeterminada a propósito: es lo que tenemos cuando el repo no lleva un archivo de licencia que una máquina pueda reconocer, lo cual es una decisión a tomar en lugar de un detalle. La acción expone una salida, findings — el número de dependencias marcadas, escrito incluso cuando es 0.
Lo que lee
- package.json — las claves de dependencies y devDependencies. peerDependencies y optionalDependencies no se leen.
- requirements.txt — un nombre por línea, comentarios eliminados, líneas que comienzan con - descartadas. Así que -r other-requirements.txt no se sigue y -e . es ignorado.
- pyproject.toml — solo claves cuyo valor comienza con una comilla o una llave, que es el estilo de Poetry: fastapi = "^0.110". Un bloque PEP 621, dependencies = ["fastapi>=0.110"], no produce nada en absoluto.
- go.mod — líneas con sangría de la forma name vX.
las rutas pueden apuntar a cualquier lugar en el árbol, por ejemplo apps/api/requirements.txt, pero el nombre base del archivo debe ser uno de esos cuatro. Un archivo llamado requirements-dev.txt no tiene un analizador: se omite sin un mensaje.
Cómo falla
- No hay manifiesto en la raíz, o no hay paso de checkout: "No se encontró ningún manifiesto de dependencia — nada que verificar." findings es 0 y el trabajo es verde. Una ejecución verde no significa un árbol limpio — lee esa línea.
- El evento no tiene una solicitud de extracción, por ejemplo en push: "No hay solicitud de extracción en contexto — comentario omitido." Los hallazgos aún están en el registro.
- pull-requests: escribir faltante: "No se pudo publicar el comentario (HTTP 403)." El trabajo permanece verde y el comentario nunca aparece. Mira el registro, no la solicitud de extracción.
- Cuota diaria de API alcanzada a mitad de ejecución: ::warning::Cuota diaria de API alcanzada — ejecución parcial. Deja de buscar cosas y comenta sobre lo que logró resolver. Una llamada por dependencia distinta, así que 300 dependencias gastan 300 de las 500 llamadas diarias de una clave gratuita.
- Un manifiesto que no se puede analizar: "⚠ <file> ilegible (…) — omitido". Los otros manifiestos aún se ejecutan.
- El paso compuesto ejecuta node desde el PATH y utiliza fetch nativo. Los runners alojados en GitHub ya llevan Node 20. Un runner autoalojado necesita actions/setup-node con Node 20 o posterior.
- licencias: '' con health-floor: '0' produce cero hallazgos para siempre. Esa combinación apaga ambas verificaciones.
Lo que falta en la Acción
Un nombre de paquete no es un nombre de repo. Cada nombre pasa por /api/v1/search?q=<name>&limit=5 y la acción mantiene un resultado solo cuando el nombre del proyecto es igual al nombre del paquete, ignorando mayúsculas y minúsculas. Cualquier otra cosa se descarta en silencio — sin línea de comentario, sin advertencia. Adivinar el resultado más cercano levantaría alarmas sobre el proyecto incorrecto, lo cual es peor que quedarse en silencio, pero significa que la verificación cubre menos que tu árbol de dependencias.
- Los paquetes npm con scope pierden su scope antes de la búsqueda: @types/node se busca como node, lo que puede coincidir con un repo no relacionado de ese nombre. Este es el único caso donde el proyecto incorrecto puede terminar en el comentario.
- Los módulos de Go mantienen su ruta: github.com/gin-gonic/gin se convierte en gin-gonic/gin, que nunca es igual a un nombre de repo (gin). Las dependencias de go.mod no se resuelven.
- Un paquete cuyo repo tiene un nombre diferente — el caso común en Python — nunca se resuelve.
- Solo se examinan los cinco mejores resultados y solo se utiliza la primera coincidencia exacta. Cuando dos repos comparten un nombre, el que tiene más estrellas gana.
La línea para leer en el registro es: Resolved N of M dependencies · K flagged. Si N está muy por debajo de M, la verificación miró una fracción de tu árbol. Nada en el comentario de la solicitud de extracción dice eso.
Especificación OpenAPI
Un archivo describe la API de lectura: https://olud.ai/openapi.json. OpenAPI 3.0.3, un servidor (https://olud.ai/api/v1), nueve operaciones GET, un esquema de seguridad — una clave de API en el encabezado X-Api-Key. No escribes una integración; pegas una dirección.
- /meta — fecha de construcción y conteo de proyectos. Declarado sin seguridad: es la verificación de salud.
- /search — q requerido, limit 1 a 50, por defecto 15.
- /projects — lang, license, vertical, health_min 0 a 100, sort en stars|health|momentum|recent (por defecto stars), limit 1 a 100 (por defecto 25), offset 0 a 100000 (por defecto 0).
- /project/{id} — el registro completo combinado, incluyendo salud con sus componentes.
- /emerging — limit 1 a 100, por defecto 25.
- /alternatives/{product} — alternativas de código abierto a un producto comercial.
- /models — provider, tier, limit 1 a 200 (por defecto 50), offset.
- /hf — más descargados y en tendencia en Hugging Face.
- /history/{slug} — 90 días de estrellas, salud y momentum. Pro y Org.
Impórtalo
- Custom GPT — Configurar, luego Acciones, luego Importar desde URL. Autenticación: Clave API, encabezado personalizado, nombre X-Api-Key.
- Dify, Flowise, Open WebUI — añade una herramienta desde un esquema OpenAPI, pega la URL, elige las operaciones que deseas exponer, añade el mismo encabezado.
- Postman, Insomnia — Importar, luego Enlace. Obtienes las nueve solicitudes, documentadas. Establece X-Api-Key una vez a nivel de colección para que cada solicitud la herede.
- Generadores de código — cualquier generador OpenAPI produce un cliente tipado, TypeScript, Python o Go, solo a partir de este archivo.
Cualquiera que sea la herramienta, solo hay dos cosas que configurar: la URL del archivo y la clave como un encabezado llamado X-Api-Key. Si una importación muestra nueve operaciones pero cada llamada responde 401, el encabezado no se está enviando — eso es lo primero que hay que verificar.
Las cuotas son por clave y por día, se reinician a las 00:00 UTC: 500 solicitudes en Free, 5,000 en Dev, 50,000 en Pro, 500,000 en Org. Se cuentan por separado del servidor MCP, así que un asistente que hace preguntas en tu editor nunca consume este presupuesto.
Cuatro feeds RSS
Sin clave, sin registro, sin cuenta. feed.php acepta exactamente cuatro tipos: noticias, blog, lanzamientos, emergentes.
| URL | Lo que lleva | De dónde proviene |
|---|---|---|
| /feed.php | Hasta 30 nuevos proyectos (★stars · owner/name), los nuevos modelos del día (Nuevo modelo: name (provider), con la ventana de contexto en la descripción), nuevos Hugging Face Spaces (Nuevo Space: name por autor, con el conteo de likes), y el artículo del día. | today-data.json, reconstruido cada hora |
| /feed.php?type=releases | Hasta 60 versiones lanzadas por los proyectos que seguimos: título del proyecto + etiqueta, enlace a la versión, guid owner/name@tag. | releases-data.json, reconstruido cada hora |
| /feed.php?type=emerging | Hasta 40 proyectos emergentes — saludables, en aceleración, aún poco conocidos. Descripción: ★stars · salud N/100 · +N estrellas esta semana. | el gráfico, reconstruido cada mañana |
| /feed.php?type=blog | Artículos bajo /blog/<slug>/, /blog/alternatives/ y /reports/. El título y la descripción son el propio título de la página y la meta descripción; la fecha es el tiempo de modificación del archivo. | leer desde el disco, así que un nuevo artículo aparece por sí solo |
Los cuatro responden RSS 2.0 como application/rss+xml, en inglés, el ítem más nuevo primero, con Cache-Control: public, max-age=1800. Eso son 30 minutos: un lector que consulta más a menudo obtiene la copia en caché, que es la misma respuesta servida más rápido.
Los Guids son estables y no siempre el enlace, que es lo que deseas al desduplicar en una automatización: las versiones usan owner/name@tag, los proyectos emergentes usan emerging:<id>, el artículo del día usa su URL más la fecha, todo lo demás usa su enlace.
Apunta Slack, Teams o Feedly a ellos para leer, o usa el disparador RSS en n8n, Zapier y Make cuando un horario te convenga mejor que un webhook — esa también es la forma de obtener versiones sin un plan de pago. Si un archivo fuente no ha sido reconstruido, el feed aún responde 200 con un canal vacío en lugar de un error, así que una automatización que de repente no recibe nada no está necesariamente rota.