POST /login
Intercambia credenciales de larga duración por un access token JWT de corta duración.
POST /api/v1/fe/login
Content-Type: application/jsonEste es el único endpoint que no requiere Authorization header.
El body ES la credencial.
Request body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
apiKeyId | string | ✅ | Identificador público de la key. Formato fak_ + 12 hex. |
apiSecret | string | ✅ | Secret asociado. Bcrypt-verificado en el servidor. |
{
"apiKeyId": "fak_a1b2c3d4e5f6",
"apiSecret": "abcd1234ef56789012345678..."
}Response — 200 OK
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 3600,
"environment": "ecf",
"scopes": [
"invoices:write",
"invoices:read",
"usage:read",
"billing:read"
]
}| Campo | Descripción |
|---|---|
accessToken | El JWT. Pásalo en Authorization: Bearer <token>. |
tokenType | Siempre "Bearer". |
expiresIn | Segundos hasta que el JWT expire. Default: 3600. |
environment | Ambiente DGII de la key (ecf, certecf, testecf). |
scopes | Scopes permitidos por esta key. |
Response — 401 Unauthorized
{
"message": "Invalid credentials",
"statusCode": 401
}El mensaje es idéntico para todas las causas de fallo (key no existe, secret incorrecto, key revocada, key expirada). Esto es intencional — evita timing oracles. Revisa el panel de Digimart para diagnosticar.
Notas de seguridad
- El servidor ejecuta una comparación
bcryptincluso cuando la key no existe, usando un hash dummy. Esto mantiene constante el tiempo de respuesta (~200ms) y previene enumeración de keys vía análisis de timing. - El
apiSecretse guarda en la DB comobcrypthash (rounds=12). Nunca se almacena en texto plano. Si lo pierdes, no podemos recuperarlo — debes crear una key nueva. - El
apiKeyIdes público y puede aparecer en logs sin riesgo.
Tiempo de respuesta esperado
| Percentil | Latencia |
|---|---|
| p50 | ~50ms |
| p95 | ~250ms (dominado por bcrypt) |
| p99 | ~500ms |
Si ves latencias > 1 segundo de forma consistente, contáctanos.
Rate limit
Throttle de dos capas para frenar brute-force de secrets:
| Layer | Trigger | Lockout |
|---|---|---|
| Por IP | 30 intentos / 60s | 15 min |
Por apiKeyId | 5 intentos / 60s | 15 min |
El JWT dura 1 hora — una llamada por hora por servicio backend es lo
normal y queda muy por debajo del threshold. Si recibes 429 Too Many Requests, respeta el header Retry-After y haz backoff exponencial.
En el panel de la derecha tienes un playground interactivo para probar este endpoint con tus credenciales reales. La llamada se hace desde nuestro Next.js server-side al FE API real — exactamente como haría tu backend.