Autenticación
API keys (qapi_test_ / qapi_live_), JWT, sistema dual-auth y scopes de permisos.
Autenticación
QAPI soporta dos mecanismos de autenticación — API keys para integraciones de backend y JWT para sesiones de usuario desde el dashboard.
API Keys
Las API keys son la forma recomendada para integrar QAPI en tu software. Cada key pertenece a un proyecto y su entorno lo define el proyecto. Tu Proyecto Sandbox y su key de prueba se crean automáticamente al registrar tu cuenta.
Formato
| Prefijo | Entorno | Descripción |
|---|---|---|
qapi_test_... | Test | Key del Proyecto Sandbox. Solo certificador MOCK. No consume cuota. No toca la SAT. |
qapi_live_... | Live | Key de un proyecto live. Usa el certificador real. Consume la cuota del proyecto. Factura real ante la SAT. |
La API key es recuperable
No la pierdes si no la copiaste al crearla: podés volver a ver y copiar la key completa desde el dashboard, dentro del proyecto (ícono del ojo). Cada proyecto tiene una key activa — para rotarla, regenerala (eso revoca la anterior). Cada vez que se revela, queda registrado en el log de auditoría de tu cuenta.
Uso en requests
Envía la API key en el header Authorization con el prefijo Bearer:
Authorization: Bearer qapi_test_abc123xyz...Acceso de la key (scopes)
Al crear una key elegís su nivel de acceso, y el API lo hace cumplir:
| Acceso | Scopes | Permite |
|---|---|---|
| Completo | invoices:write + invoices:read | Emitir, anular y consultar facturas |
| Solo lectura | invoices:read | Solo consultar (GET) — no puede emitir ni anular |
Una key solo lectura que intente emitir (POST /v1/invoices) o anular
(POST /v1/invoices/:id/cancel) recibe 403 FORBIDDEN indicando el scope que le falta.
Útil, por ejemplo, para una key de un panel de monitoreo que solo lee y nunca debe facturar.
Cómo elegirlo
Lo elegís al generar la key en el dashboard (dentro de cada proyecto). La key del Sandbox y la inicial se crean con acceso completo. Al regenerar una key se conserva su mismo acceso.
JWT (JSON Web Tokens)
Los JWT autentican sesiones de usuario desde el navegador (el dashboard y el playground en
/v1/dashboard/* y /v1/sandbox/*). Es el mecanismo que usa la propia app web de QAPI.
Para integrar, usá API keys — no JWT
Si estás conectando tu software a QAPI (servidor a servidor), usá una API key: es un solo header, sin expiración corta ni cookies. El flujo JWT de abajo está pensado para el dashboard en el navegador (incluye una cookie HttpOnly y protección CSRF que un cliente de servidor no necesita manejar).
Obtener tokens
POST /v1/auth/login
Content-Type: application/json
{
"email": "tu@email.com",
"password": "tupassword"
}Respuesta:
{
"access_token": "eyJhbGci...",
"user": { "id": "...", "email": "...", "nombre": "...", "role": "owner", "mfa_enabled": false },
"tenant": { "id": "...", "nombre": "...", "plan": "starter", "certificador": "MOCK" }
}El refresh_token no viene en el body: QAPI lo setea en una cookie HttpOnly (qapi_rt)
inaccesible a JavaScript. El cliente solo guarda el access_token.
Login con verificación en dos pasos (MFA)
Si la cuenta tiene MFA activo (obligatorio para superadmin), /v1/auth/login no devuelve los
tokens: responde { "mfa_required": true, "mfa_token": "..." } (o mfa_enrollment_required
si es un superadmin que aún no lo configuró). Completá el login enviando el mfa_token y el
código TOTP (o un backup code) a POST /v1/auth/mfa/verify, que recién ahí devuelve
access_token + user + tenant y setea la cookie del refresh.
Ciclo de vida
| Token | Duración | Dónde vive |
|---|---|---|
access_token | 15 minutos | El cliente lo guarda y lo manda como Bearer en cada request |
refresh_token | 7 días | Cookie HttpOnly qapi_rt que setea el backend (el cliente no la maneja) |
Renovar el access token
El access token es corto (15 min). Para renovarlo, el navegador llama a POST /v1/auth/refresh:
el refresh viaja solo en la cookie qapi_rt (no en el body). Como la cookie se adjunta sola,
el endpoint exige protección CSRF double-submit: enviá el header X-CSRF-Token con el valor
de la cookie no-HttpOnly qapi_csrf (la setea el backend junto al refresh).
Rotación de refresh tokens
Cada vez que renuevas, el refresh anterior queda revocado y se rota la cookie. Si se reusa un refresh ya rotado, QAPI detecta la reutilización y revoca todas las sesiones del usuario.
POST /v1/auth/refresh
X-CSRF-Token: <valor de la cookie qapi_csrf>
# sin body — el refresh viaja en la cookie HttpOnly qapi_rt
# Respuesta: { "access_token": "eyJhbGci..." } (y rota la cookie qapi_rt)El logout (POST /v1/auth/logout) funciona igual: lee la cookie y exige el mismo header
X-CSRF-Token.
Dual-Auth: cómo QAPI detecta el método
QAPI detecta automáticamente el tipo de credencial según el prefijo del header Authorization:
| Valor del header | Mecanismo activado |
|---|---|
Bearer qapi_test_... o Bearer qapi_live_... | API Key |
Bearer eyJ... (comienza con eyJ) | JWT |
No hay un endpoint diferente — el mismo header, dos caminos de validación.
Rate limits de autenticación
Prop
Type
Ejemplos de autenticación
curl -X GET https://api.dtevia.com.gt/v1/invoices \
-H "Authorization: Bearer qapi_test_TU_API_KEY"# 1. Login
TOKEN=$(curl -s -X POST https://api.dtevia.com.gt/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"tu@email.com","password":"tupassword"}' \
| jq -r '.access_token')
# 2. Usar el token
curl -X GET https://api.dtevia.com.gt/v1/dashboard/api-keys \
-H "Authorization: Bearer $TOKEN"const BASE = 'https://api.dtevia.com.gt/v1';
const API_KEY = 'qapi_test_TU_API_KEY';
const res = await fetch(`${BASE}/invoices`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
});
const data = await res.json();import requests
BASE = 'https://api.dtevia.com.gt/v1'
API_KEY = 'qapi_test_TU_API_KEY'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}
response = requests.get(f'{BASE}/invoices', headers=headers)
data = response.json()