Saltar al contenido principal

API keys de integración

Archivo: api/src/routes/apiKeys.js · Prefijo: /api/api-keys

Credenciales para consumidores externos (por ejemplo, el chatbot de WhatsApp) que necesitan usar la API sin ser un usuario de la plataforma. A diferencia del login (POST /auth/login), un API key:

  • No expira por tiempo, salvo que se le ponga vencimiento: dura hasta que se revoca o se rota.
  • No tiene rol ni contraseña: solo un name y una lista de scopes.
  • No identifica a ningún cliente ni asesor: es una credencial de servicio, no de una persona.

Ver también API para el chatbot y Reglas de negocio.

Formato del key​

dvl_<32 caracteres aleatorios>

Los primeros 12 caracteres (dvl_ + 8) son el prefijo, que no es secreto y sirve para ubicar el registro. Se guarda solo el hash bcrypt del key completo (tabla api_keys). El valor completo se muestra una única vez, en la respuesta de POST /api/api-keys; después no se puede volver a consultar, solo revocar y generar uno nuevo.

Nunca publiques un key

No pongas un key real en esta documentación, en repositorios, tickets ni chats. Si un key queda expuesto, revócalo de inmediato (DELETE /api/api-keys/:id) y genera uno nuevo. Entrégalo al equipo externo por un canal seguro (gestor de secretos o enlace de un solo uso).

Scopes disponibles​

Cada credencial debe tener solo los scopes que necesita (mínimo privilegio).

ScopeHabilita
catalog:readGET /api/categories
clients:readGET /api/integrations/clients/lookup: identificar clientes por WhatsApp
prices:readGET /api/integrations/clients/:clientId/prices: precios por cliente
cutoff:readGET /api/integrations/cutoff: ventana de corte
quotations:writePOST /api/integrations/quotations y GET /api/integrations/quotations/:id (solo las creadas con el mismo key)

El chatbot de WhatsApp necesita clients:read, prices:read, cutoff:read y quotations:write.

GET /api/products/catalog ya es público y no requiere key. Deliberadamente no hay scope para GET /api/promotions ni GET /api/price-lists: esos endpoints exponen qué clientes tienen cada promoción o lista. El chatbot obtiene el precio ya calculado por cliente con prices:read.


GET /api/api-keys​

Lista los API keys (sin el hash ni el valor completo).

Acceso: admin

{
"items": [
{
"id": "…", "name": "Chatbot WhatsApp", "keyPrefix": "dvl_xxxxxxxx",
"scopes": ["clients:read", "prices:read", "cutoff:read", "quotations:write"], "active": true,
"lastUsedAt": "2026-10-05T18:03:48.826Z",
"createdAt": "2026-10-05T18:03:48.496Z", "revokedAt": null,
"expiresAt": null, "replacedBy": null
}
]
}

POST /api/api-keys​

Genera un API key nuevo.

Acceso: admin

Body

CampoTipoRequerido
namestringSí
scopesstring[], valores de la lista de arribaSí, mínimo 1
expiresInDaysentero 1–730No. Sin él, no vence
{ "name": "Chatbot WhatsApp", "scopes": ["clients:read", "prices:read", "cutoff:read", "quotations:write"] }

Respuesta 201

{
"id": "…", "name": "Chatbot WhatsApp",
"scopes": ["clients:read", "prices:read", "cutoff:read", "quotations:write"],
"apiKey": "dvl_…",
"createdAt": "…",
"expiresAt": null
}
Guarda el apiKey de inmediato

Es la única respuesta que lo incluye completo. Si se pierde, hay que revocar este key y generar uno nuevo.

PATCH /api/api-keys/:id​

Cambia name y/o scopes de un key activo sin cambiar su valor. Sirve para ajustar permisos (mínimo privilegio) sin coordinar un cambio de credencial con el consumidor.

Acceso: admin · Body: { "scopes": ["cutoff:read", "clients:read"] } · Respuesta 200: el key (sin valor) · Errores: 404 NOT_FOUND si no existe o está revocado.

POST /api/api-keys/:id/rotate​

Renueva una credencial sin cortar el servicio: emite un key nuevo con el mismo nombre y deja el anterior funcionando durante un periodo de gracia, para que el consumidor cambie al nuevo.

Acceso: admin

Body (todo opcional)

CampoTipoPor defectoDescripción
graceHoursentero 0–16848Horas que el key anterior sigue vivo. 0 lo revoca de inmediato
scopesstring[]los del key anteriorScopes del key nuevo
expiresInDaysentero 1–730sin vencimientoVencimiento del key nuevo

Respuesta 201

{
"id": "…", "name": "Chatbot WhatsApp",
"scopes": ["clients:read", "prices:read", "cutoff:read", "quotations:write"],
"apiKey": "dvl_…",
"createdAt": "…", "expiresAt": null,
"previous": { "id": "…", "expiresAt": "2026-10-07T21:20:42.796Z" }
}

El key anterior queda con replacedBy apuntando al nuevo. Al vencer, responde 401 API_KEY_EXPIRED.

Errores: 404 NOT_FOUND (no existe o revocado), 409 API_KEY_EXPIRED (ya venció: genera uno nuevo con POST).

DELETE /api/api-keys/:id​

Revoca el key de inmediato (active = false). No lo borra: conserva el registro para auditoría. Úsalo cuando un key quedó expuesto; para una renovación planeada usa rotate.

Acceso: admin

Respuesta 204 · Errores: 404 NOT_FOUND si no existe o ya estaba revocado.


Cómo lo usa la integración​

En la cabecera Authorization, igual que un token de usuario:

curl https://<dominio>/api/integrations/cutoff?branchId=<uuid> \
-H "Authorization: Bearer $DAVAL_API_KEY"

api/src/middleware/apiKey.js reconoce el prefijo dvl_. Hay dos tipos de endpoint:

  • Exclusivos de integraciones (/api/integrations/*, requireApiKeyScope): solo aceptan API key. Sin key responden 401 API_KEY_REQUIRED; con un key sin el scope, 403 INSUFFICIENT_SCOPE. El login de un usuario no sirve aquí.
  • Compartidos (GET /api/categories, requireScopeOrAuth): aceptan el login normal o un key con el scope. Un key sin el scope cae al login normal y termina en 401 INVALID_TOKEN.

Un key inválido o revocado responde 401 INVALID_API_KEY; uno vencido, 401 API_KEY_EXPIRED.

Procedimiento completo de entrega, renovación y revocación: Credenciales y seguridad.