Manejo de Errores
Catálogo completo de errores RFC 7807 de QAPI y cómo manejarlos en tu integración.
Manejo de Errores
QAPI usa el estándar RFC 7807 para todos los errores. Cada respuesta de error
tiene una URL type que apunta a la documentación específica del error.
Formato de error
Ejemplo de respuesta
{
"type": "https://docs.dtevia.com.gt/errors/validation-error",
"title": "VALIDATION_ERROR",
"status": 422,
"detail": "Error de validación",
"instance": "/v1/invoices",
"invalid_params": [
{
"field": "items.0.impuestos.0.monto_impuesto",
"message": "MontoImpuesto IVA inválido. Esperado ~12.00, recibido 10.00"
}
]
}Campos del objeto
| Campo | Descripción |
|---|---|
type | URL de documentación del error. Siempre resuelve a una página en esta documentación. |
title | Código del error en MAYÚSCULAS (constante, seguro para comparar en código) |
status | Código HTTP |
detail | Descripción legible en español |
instance | Endpoint que generó el error |
invalid_params | Solo en VALIDATION_ERROR. Lista de {field, message} con los campos que fallaron. |
Catálogo de errores
error_code de factura ≠ errores HTTP
Una factura FAILED trae error_code: "SIGN_FAILED" o "CERT_FAILED" (también en el
webhook invoice.failed). Son códigos del pipeline de certificación, un namespace
distinto a los errores HTTP de este catálogo.
Manejo de errores en código
Por tipo de error
interface QapiError {
type: string;
title: string;
status: number;
detail: string;
invalid_params?: { field: string; message: string }[];
}
async function emitirFactura(payload: InvoicePayload): Promise<Invoice> {
const res = await fetch('https://api.dtevia.com.gt/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.QAPI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!res.ok) {
const error: QapiError = await res.json();
switch (error.status) {
case 422:
// Error de validación — muéstrale al usuario los campos inválidos
const fields = error.invalid_params?.map(p => `${p.field}: ${p.message}`).join('\n');
throw new ValidationError(`Factura inválida:\n${fields}`);
case 429:
// Rate limit — implementa backoff
const retryAfter = res.headers.get('retry-after');
throw new RateLimitError(`Reintenta en ${retryAfter}s`);
case 401:
case 403:
throw new AuthError(error.detail);
default:
// Error del servidor — loggea el X-Request-ID para soporte
const requestId = res.headers.get('x-request-id');
throw new Error(`Error QAPI [${requestId}]: ${error.detail}`);
}
}
return res.json();
}import axios, { AxiosError } from 'axios';
async function emitirFactura(payload: InvoicePayload): Promise<Invoice> {
try {
const { data } = await axios.post(
'https://api.dtevia.com.gt/v1/invoices',
payload,
{ headers: { Authorization: `Bearer ${process.env.QAPI_API_KEY}` } }
);
return data;
} catch (err) {
if (!axios.isAxiosError(err) || !err.response) throw err;
const error = err.response.data;
if (error.status === 422 && error.invalid_params) {
const fields = error.invalid_params.map((p: any) => `${p.field}: ${p.message}`);
throw new Error(`Validación fallida: ${fields.join('; ')}`);
}
throw new Error(`QAPI Error ${error.status}: ${error.detail}`);
}
}class QapiValidationError(Exception):
def __init__(self, invalid_params: list):
self.invalid_params = invalid_params
fields = ", ".join(f"{p['field']}: {p['message']}" for p in invalid_params)
super().__init__(f"Validación fallida: {fields}")
def emitir_factura(payload: dict) -> dict:
res = httpx.post(
"https://api.dtevia.com.gt/v1/invoices",
json=payload,
headers={"Authorization": f"Bearer {QAPI_API_KEY}"},
)
if res.status_code != 202:
error = res.json()
if error["status"] == 422:
raise QapiValidationError(error.get("invalid_params", []))
raise Exception(f"QAPI Error {error['status']}: {error['detail']}")
return res.json()URLs de documentación de errores
Cómo funcionan los enlaces type
El campo type de cada error apunta a https://docs.dtevia.com.gt/errors/{slug}.
Estas URLs están configuradas como redirects a páginas específicas de esta documentación
que explican la causa y la solución de cada error.
Si tu backend genera un error con type: "https://docs.dtevia.com.gt/errors/validation-error",
un desarrollador que haga click en esa URL llega directamente a la explicación de
las 14 validaciones. No necesitas documentar los errores tú mismo.