Autenticación

Toda la API se autentica con una clave de portador (bearer token). Cada clave pertenece a una organización y lleva un conjunto explícito de permisos.

Formato del token

Un secreto válido tiene este aspecto y se compone de un prefijo legible (16 caracteres) y un secreto aleatorio de 32 bytes codificado en base64url:

formato
rud1_sk_a1b2c3d4_8tHX1nL...zQ
└──┬──┘ └───┬──┘ └──────┬──────┘
prefijo  id key      secreto

Lo envías en cada petición como header HTTP estándar:

header
Authorization: Bearer rud1_sk_a1b2c3d4_8tHX1nL...zQ

⚠️ Trata la clave como una contraseña. Nunca la publiques en frontend, repos abiertos o screenshots. Cuando contactes con soporte, comparte solo los 16 primeros caracteres (el prefijo).

Cómo se almacena

Solo el prefijo se guarda en claro (para que veas la clave en el listado). El secreto completo se hashea con bcrypt (factor de coste 10) y solo se compara con bcrypt.compare en tiempo constante. Esto significa que:

  • Si pierdes la clave, no podemos recuperarla — tendrás que revocarla y crear una nueva.
  • Una filtración de la base de datos no expone tus secretos: el atacante tendría que romper bcrypt por fuerza bruta.

Scopes (permisos)

Las claves no heredan los permisos de tu rol — eliges cada scope de forma explícita al crearla, lo que permite emitir claves de privilegio mínimo para cada integración.

ScopeConcede
devices:readListar y ver detalle de dispositivos
devices:write(Reservado) Operaciones de escritura sobre dispositivos
metrics:readLeer series temporales de métricas
logs:readLeer eventos y registros del dispositivo
alerts:readListar alertas activas, reconocidas o resueltas
alerts:writeReconocer y resolver alertas programáticamente

💡 Crea una clave por integración. Si un servicio solo lee métricas, emítele metrics:read y nada más. Si más tarde necesitas reaccionar a alertas desde otro servicio, crea una segunda clave con alerts:write y revoca cada una por separado cuando deje de hacer falta.

Ciclo de vida

Creación

En Ajustes → Claves API: nombre, scopes, expiración (30, 90, 180, 365 días o nunca). El secreto aparece una sola vez tras pulsar Generar clave.

Rotación

No hay un endpoint de rotación: emite una nueva clave, despliega el secreto en tu integración, verifica que funciona y revoca la anterior. Esto deja una ventana en la que ambas claves son válidas — útil para deploys con cero downtime.

Expiración

Si configuraste una caducidad, las peticiones empezarán a responder 401 expired_api_key en cuanto pase la fecha. Crea una nueva clave antes de que expire para evitar cortes.

Revocación

Pulsando el icono de la papelera junto a la clave en el listado. La revocación es inmediata: a partir del siguiente request la clave devuelve 401 invalid_api_key. La fila queda visible en el listado con el badge "Revocada" para que tengas registro histórico.

Rate limiting

Cada clave puede hacer hasta 60 peticiones por minuto en una ventana deslizante. Si pasas el límite recibirás 429 rate_limit_exceeded con un header Retry-After en segundos.

Cada respuesta —tanto exitosa como con error— incluye estos headers para que pacees proactivamente sin necesidad de reaccionar al 429:

HeaderSignificado
X-RateLimit-LimitMáximo de peticiones por ventana (60)
X-RateLimit-RemainingPeticiones restantes en la ventana actual
X-RateLimit-ResetTimestamp Unix (segundos) en el que la ventana se reinicia
Retry-After(solo en 429) Segundos a esperar antes de reintentar

ℹ️ El rate limiter actual es per-instancia. En nuestra infraestructura Vercel + Neon, cada función serverless mantiene su propio contador en memoria, por lo que el límite efectivo puede ser mayor al nominal cuando hay varios contenedores atendiendo. Si necesitas garantías estrictas de cuota, contáctanos y la migramos a un store distribuido (Upstash Redis).

Ejemplo: gestión de errores y rate limit

javascript
async function rud1Get(path: string) {
  const res = await fetch(`https://www.rud1.es${path}`, {
    headers: { Authorization: `Bearer ${process.env.RUD1_API_KEY}` },
  });

  // Pace yourself: read the rate limit headers proactively
  const remaining = Number(res.headers.get("X-RateLimit-Remaining"));
  if (remaining < 5) {
    const reset = Number(res.headers.get("X-RateLimit-Reset")) * 1000;
    const wait = Math.max(0, reset - Date.now());
    if (wait > 0) await new Promise((r) => setTimeout(r, wait));
  }

  if (res.status === 429) {
    const retryAfter = Number(res.headers.get("Retry-After")) * 1000;
    await new Promise((r) => setTimeout(r, retryAfter));
    return rud1Get(path); // single retry
  }

  if (!res.ok) {
    const err = await res.json();
    throw new Error(`${res.status} ${err.error.code}: ${err.error.message}`);
  }
  return res.json();
}