Estados de Factura
Ciclo de vida completo de una factura QAPI — de PENDING a CERTIFIED, FAILED o CANCELLED.
Estados de Factura
Cada factura en QAPI pasa por un ciclo de vida bien definido. El campo status en
GET /v1/invoices/:id siempre refleja el estado actual.
Diagrama de estados
POST /v1/invoices (responde 202 con status "PROCESSING")
│
▼
PENDING ──────────────────────────────────────────┐
│ (pipeline async inicia) │
▼ │
SIGNING ── firma del XML falla ──────► FAILED │
│ │
▼ │
CERTIFYING ── certificador falla ─────► FAILED │
│ │
└──── certificador responde OK ──► CERTIFIED │
│
CERTIFIED ──── POST /invoices/:id/cancel ──► CANCELLEDPROCESSING solo aparece en la respuesta 202
El body del 202 Accepted devuelve status: "PROCESSING" como resumen de "tu factura
está en el pipeline". Al consultar con GET /v1/invoices/:id verás el estado persistido
real: PENDING, SIGNING, CERTIFYING, o uno de los finales. Trata los tres
transitorios como equivalentes: "todavía no hay resultado, sigue esperando".
Detalle de cada estado
Prop
Type
Campos disponibles por estado
CERTIFIED
{
"id": "inv_01jx...",
"status": "CERTIFIED",
"tipo_dte": "FACT",
"moneda": "GTQ",
"gran_total": 112.00,
"numero_acceso": 543219876,
"certificador": "TOTALDOC",
"uuid_sat": "550E8400-E29B-41D4-A716-446655440000",
"serie": "A",
"numero_dte": 42,
"pdf_url": "https://print.totaldoc.io/pdf?uuid=550E8400-...",
"referencia_interna": "ORD-2026-001",
"integrity_checksum": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"created_at": "2026-06-08T16:00:00.000Z",
"updated_at": "2026-06-08T16:00:02.000Z",
"certified_at": "2026-06-08T16:00:02.000Z"
}FAILED
{
"id": "inv_01jx...",
"status": "FAILED",
"tipo_dte": "FACT",
"gran_total": 112.00,
"certificador": "TOTALDOC",
"error_code": "CERT_FAILED",
"error_message": "NIT del emisor no encontrado en el padrón SAT",
"created_at": "2026-06-08T16:00:00.000Z",
"updated_at": "2026-06-08T16:00:05.000Z"
}CANCELLED
{
"id": "inv_01jx...",
"status": "CANCELLED",
"tipo_dte": "FACT",
"gran_total": 112.00,
"uuid_sat": "550e8400-e29b-41d4-a716-446655440000",
"certified_at": "2026-06-08T16:00:02.000Z",
"cancelled_at": "2026-06-09T10:30:00.000Z"
}Transiciones permitidas
| Desde | Hacia | Cómo |
|---|---|---|
PENDING | SIGNING | Automático — pipeline interno inicia |
SIGNING | CERTIFYING | Automático — XML firmado correctamente |
SIGNING | FAILED | Automático — la firma falla (error_code: SIGN_FAILED) |
CERTIFYING | CERTIFIED | Automático — certificador responde OK |
CERTIFYING | FAILED | Automático — certificador falla o timeout (error_code: CERT_FAILED) |
CERTIFIED | CANCELLED | Manual — POST /v1/invoices/:id/cancel |
Solo CERTIFIED se puede cancelar
Si intentas cancelar una factura que no está en estado CERTIFIED (por ejemplo SIGNING,
CERTIFYING o FAILED), recibirás 409 INVALID_STATUS.
FAILED no es cancelable
Una factura FAILED no tiene validez tributaria — no llegó a la SAT. No es necesario cancelarla.
Simplemente crea una nueva factura con los datos corregidos.
Polling recomendado
En entorno live, la certificación tarda entre 1 y 10 segundos dependiendo del certificador. En entorno test (MOCK), es casi instantáneo.
Estrategia de polling con backoff:
async function waitForCertification(id: string, apiKey: string) {
const delays = [1000, 2000, 2000, 3000, 3000, 5000, 5000, 10000];
for (const delay of delays) {
await new Promise(r => setTimeout(r, delay));
const res = await fetch(`https://api.dtevia.com.gt/v1/invoices/${id}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const invoice = await res.json();
if (invoice.status === 'CERTIFIED') return invoice;
if (invoice.status === 'FAILED') throw new Error(invoice.error_message);
}
throw new Error('Timeout: la certificación tardó más de lo esperado');
}Como alternativa al polling, configura un webhook para recibir
invoice.certified e invoice.failed automáticamente.
Webhook events por estado
| Estado final | Evento webhook disparado |
|---|---|
CERTIFIED | invoice.certified |
FAILED | invoice.failed |
CANCELLED | invoice.cancelled |
Los estados transitorios (PENDING, SIGNING, CERTIFYING) no disparan webhooks —
solo los finales. El evento invoice.test se dispara manualmente desde el dashboard.
El error_code de una factura FAILED es SIGN_FAILED (falló la firma) o CERT_FAILED
(falló la certificación) — es un namespace distinto al de los errores HTTP del
catálogo de errores.
Objeto Factura
Esquema completo del request POST /v1/invoices con todos los campos, tipos y restricciones.
Iniciar sesión
Autentica con email y contraseña. Devuelve el `access_token` (JWT) junto con el perfil del usuario y del tenant. El `refresh_token` NO viene en el body: QAPI lo setea en una cookie HttpOnly `qapi_rt` (inaccesible a JavaScript). 💡 **Guardá el `access_token`**: es el que vas a usar como `Authorization: Bearer <token>` para probar los endpoints de **Sandbox** desde esta misma documentación. Dura **15 minutos**; renovalo con `POST /v1/auth/refresh` (que usa la cookie). 🔐 **MFA**: si la cuenta tiene verificación en dos pasos activa (obligatoria para superadmin), este endpoint NO devuelve tokens — responde `{ "mfa_required": true, "mfa_token": "..." }` (o `mfa_enrollment_required` si el superadmin aún no lo configuró). Completá el login con `POST /v1/auth/mfa/verify`. > Para **integrar tu software** con QAPI usá una **API key**, no este flujo JWT (pensado para el dashboard en el navegador). Protegido contra fuerza bruta: máximo 10 intentos por email cada 15 minutos.