Anular una Factura
Cómo cancelar una factura CERTIFIED ante la SAT usando POST /v1/invoices/:id/cancel.
Anular una Factura
En Guatemala, anular una factura electrónica es un proceso formal ante la SAT.
QAPI expone POST /v1/invoices/:id/cancel para ejecutarlo.
Solo facturas CERTIFIED se pueden anular
Solo se pueden cancelar facturas en estado CERTIFIED. Intentar cancelar una factura
PENDING, PROCESSING, FAILED o CANCELLED retorna 409 INVALID_STATUS.
Antes de anular
Considera si realmente necesitas anular o si es más apropiado emitir una Nota de Crédito
(NCRE) para ajustes parciales. La anulación es permanente e irreversible.
| Caso | Acción recomendada |
|---|---|
| Error total en la factura | Anulación (cancel) |
| Devolución parcial | Nota de Crédito (NCRE) |
| Ajuste de precio | Nota de Débito (NDEB) o Nota de Crédito |
Cómo cancelar
Verifica el estado actual
Asegúrate de que la factura está en estado CERTIFIED:
curl https://api.dtevia.com.gt/v1/invoices/inv_01jxyz \
-H "Authorization: Bearer qapi_live_xxxxxxxxxxxx"const res = await fetch('https://api.dtevia.com.gt/v1/invoices/inv_01jxyz', {
headers: { Authorization: 'Bearer qapi_live_xxxxxxxxxxxx' },
});
const invoice = await res.json();
if (invoice.status !== 'CERTIFIED') {
throw new Error(`No se puede cancelar: estado actual es ${invoice.status}`);
}res = httpx.get(
"https://api.dtevia.com.gt/v1/invoices/inv_01jxyz",
headers={"Authorization": "Bearer qapi_live_xxxxxxxxxxxx"},
)
invoice = res.json()
if invoice["status"] != "CERTIFIED":
raise ValueError(f"No se puede cancelar: estado es {invoice['status']}")Envía la solicitud de cancelación
El body requiere dos campos obligatorios:
| Campo | Tipo | Reglas |
|---|---|---|
motivo | string | Mínimo 5 caracteres |
fecha_anulacion | string | ISO 8601 con timezone Guatemala: YYYY-MM-DDTHH:mm:ss-06:00 |
El offset -06:00 es obligatorio
fecha_anulacion debe terminar exactamente en -06:00 (hora de Guatemala).
Enviar Z, otro offset u omitir el campo retorna 422 VALIDATION_ERROR.
curl -X POST https://api.dtevia.com.gt/v1/invoices/inv_01jxyz/cancel \
-H "Authorization: Bearer qapi_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"motivo": "Error en datos del receptor",
"fecha_anulacion": "2026-06-09T10:30:00-06:00"
}'const res = await fetch('https://api.dtevia.com.gt/v1/invoices/inv_01jxyz/cancel', {
method: 'POST',
headers: {
Authorization: 'Bearer qapi_live_xxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
motivo: 'Error en datos del receptor',
fecha_anulacion: '2026-06-09T10:30:00-06:00',
}),
});
const invoice = await res.json();
// invoice.status === "CANCELLED"const { data } = await axios.post(
'https://api.dtevia.com.gt/v1/invoices/inv_01jxyz/cancel',
{
motivo: 'Error en datos del receptor',
fecha_anulacion: '2026-06-09T10:30:00-06:00',
},
{ headers: { Authorization: 'Bearer qapi_live_xxxxxxxxxxxx' } }
);
// data.status === "CANCELLED"res = httpx.post(
"https://api.dtevia.com.gt/v1/invoices/inv_01jxyz/cancel",
json={
"motivo": "Error en datos del receptor",
"fecha_anulacion": "2026-06-09T10:30:00-06:00",
},
headers={"Authorization": "Bearer qapi_live_xxxxxxxxxxxx"},
)
invoice = res.json()
# invoice["status"] == "CANCELLED"Verifica la cancelación
La respuesta exitosa es 200 con un resumen de la anulación:
{
"id": "inv_01jxyz",
"status": "CANCELLED",
"cancelled_at": "2026-06-09T16:30:00.000Z"
}Si necesitas el objeto completo de la factura (con uuid_sat, totales, etc.),
consúltalo con GET /v1/invoices/:id.
El PDF después de anular
La factura anulada conserva sus datos de certificación (uuid_sat, serie,
numero_dte, pdf_url) — la autorización de la SAT existió y es parte del rastro fiscal
del documento. El pdf_url sigue siendo accesible: el certificador re-renderiza la
representación gráfica con la marca de agua "ANULADO", que es el comprobante visual
de la anulación.
Errores comunes
| Código HTTP | Error | Causa |
|---|---|---|
409 | INVALID_STATUS | La factura no está en estado CERTIFIED |
404 | NOT_FOUND | El id no existe o no pertenece a tu tenant |
422 | VALIDATION_ERROR | Falta motivo (mín. 5 caracteres) o fecha_anulacion no tiene el formato YYYY-MM-DDTHH:mm:ss-06:00 |
502 | CERTIFIER_ERROR | El certificador rechazó la anulación |
Webhook de cancelación
Si tienes webhooks configurados, recibirás el evento invoice.cancelled cuando la
cancelación se complete:
{
"event": "invoice.cancelled",
"data": {
"id": "inv_01jxyz",
"status": "CANCELLED",
"test": false
}
}El payload de este evento trae id, status y test (true si la factura se emitió con
el certificador MOCK —sandbox o cuenta sin activar—, false en emisiones reales);
la fecha de anulación está en cancelled_at vía GET /v1/invoices/:id.
Ver la guía de Webhooks para la configuración completa.