API
Referencia HTTP de la API pública de UBFB (Bun + Express). Todo lo listado aquí como público no requiere clave alguna.
Resumen
Base URL
https://ubfb-api.theindiebrand.es
Límite global
300 peticiones/min por IP
Escritura
20/min en POST /reports
Autenticación
Todo público, salvo Apelaciones
Blacklist
Lecturas públicas sobre el estado decidido — solo entradas Activo.
/blacklist/ids{ "ids": ["123456789012345678", "234567890123456789"] }/blacklist/all{ "entries": [{ "id": "123456789012345678", "reason": "Dox", "status": "Activo", "punishmentDays": 7 }] }/blacklist/:userId{ "blacklisted": true, "entry": { "id": "123456789012345678", "reason": "Dox", "status": "Activo" } }404 con { "blacklisted": false } si el usuario nunca ha estado en la blacklist.
Reportes
La cola pendiente — cualquiera puede leerla o añadir un reporte; nadie puede resolverla por API.
/reports/reportsArchiva un nuevo reporte.
- No lleva
punishmentDays— el castigo ya no lo propone quien reporta, se calcula a partir dereason(ver Motivos de blacklist). proofses obligatorio y no puede ir vacío —400si falta, no es un array, o está vacío. Cada URL se descarga, se limpia de metadatos y se re-aloja server-side antes de guardarse.409si el usuario ya tiene un reporte pendiente.201con el reporte creado —proofsen la respuesta ya son rutas locales, no las URLs originales.
{
"userId": "123456789012345678",
"reason": "Flood",
"authorId": "111111111111111111",
"authorUsername": "someone",
"intermediaryId": "222222222222222222",
"intermediaryUsername": "SomeBot",
"proofs": ["https://cdn.discordapp.com/attachments/.../screenshot.png"]
}Exportar datos
/export?format=json|csv|sqlreports ni appeals — esos datos contienen identidades de quien reporta y mensajes privados de apelación.400 si format falta o no es uno de los tres válidos.
Tiempo real (WebSocket)
Evita que los bots tengan que hacer polling constante a GET /blacklist/ids, y permite saber en todo momento cuántos/qué bots están conectados.
/wsws://<host>:3000/ws (o wss:// cuando haya TLS configurado).Al conectar
El servidor envía inmediatamente los IDs de la blacklist activa — servidos desde un cache en memoria, sin consultar la base de datos en cada conexión.
"sync"reason,proofs, timestamps, etc. en cada conexión si el cliente solo necesitaisBlacklisted(id). Si necesitas el detalle de un usuario puntual, pídelo aparte conGET /blacklist/:userId.{ "type": "sync", "ids": ["123456789012345678", "234567890123456789"] }Opcionalmente, el cliente puede identificarse enviando un mensaje hello justo después de conectar:
"hello"name y avatarUrl son públicos y no verificados — cualquiera puede poner cualquier nombre, es solo información cosmética de presencia, no autenticación. Valores inválidos, vacíos o demasiado largos se descartan en silencio.{ "type": "hello", "name": "MiBot", "avatarUrl": "https://..." }Eventos en vivo
A partir de ahí, el servidor empuja actualizaciones sin que el cliente pida nada:
"add"{ "type": "add", "entry": { "id": "123...", "reason": "Estafa", "...": "..." } }"remove"{ "type": "remove", "userId": "123..." }Quién está conectado ahora
/bots/active{ "count": 3, "bots": [{ "name": "MiBot", "avatarUrl": "https://...", "connectedAt": "2026-08-23T20:46:22.903Z" }] }Detalles de la conexión
- Heartbeat cada 30s (ping/pong) — conexiones muertas se limpian automáticamente.
- El cache de IDs activos se mantiene en memoria del proceso y se actualiza incrementalmente en cada
add/remove— nada más lo toca. - No hay autenticación ni rate-limit específico por ahora; es de solo lectura desde el lado del cliente.
Cliente oficial (ubfb)
El paquete oficial en TypeScript ya evita implementar este protocolo a mano — ver Package.
Apelaciones
Uso interno exclusivo del backend del panel web — requiere una cabecera Authorization: Bearer con un secreto compartido que no se distribuye a terceros. No están pensados para integraciones externas: la identidad del usuario ya viene verificada por el login de Discord del panel antes de llegar aquí.
/appeals/:userId/appealsActivo; solo puede haber una pendiente a la vez.Errores
Formato consistente en toda la API:
{ "error": "mensaje descriptivo" }
{ "errors": ["campo x es obligatorio", "..."] }| 400 | Validación de entrada fallida |
| 401 | Credenciales internas inválidas |
| 404 | No encontrado |
| 409 | Conflicto — ya existe algo pendiente |
| 429 | Límite de peticiones superado |
Ruta no reconocida404 con el mismo formato de error, en vez de la página HTML por defecto de Express.
{ "error": "Not found." }