SIFENDE
Guías

Manejar Errores

Cómo interpretar y manejar errores de la API de Sifende y rechazos de SIFEN en tu integración.

Esta guía explica cómo distinguir los distintos tipos de error que recibís de Sifende, qué hacer con cada uno y cuándo (no) reintentar.

Dos fuentes de error muy distintas

Mezclarlas en un mismo catch es una de las primeras causas de bugs en integraciones SIFEN.

FuenteCuándo apareceCómo se ve
API SifendeInmediato, en el HTTP responseStatus 400/401/403/404/422 + Problem Details JSON
SIFEN (rechazo asíncrono)Después del polling, una vez que SIFEN procesa el loteestado: "RECHAZADO" + mensajeRechazo con código SIFEN

Errores de la API Sifende (síncronos)

Sigan el formato RFC 9457 Problem Details (excepto autenticación):

{
  "type": "https://sifende.com.py/docs/solucion-problemas/validation-error",
  "title": "Error de validación",
  "status": 400,
  "detail": "La solicitud contiene 2 error(es) de validación",
  "errores": {
    "receptor.numeroDocumento": "Número de documento es obligatorio",
    "items[0].precioUnitario": "El precio no puede ser negativo"
  }
}

Cómo manejarlos en código

const idempotencyKey = await obtenerOCrearClaveIdempotencia(ventaId);
const DOCUMENT_QUOTA_EXCEEDED =
  'https://sifende.com.py/docs/solucion-problemas/document-quota-exceeded';

const res = await fetch('https://api.sifende.com.py/api/v1/documento-electronico', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': idempotencyKey,
  },
  body: JSON.stringify(payload),
});

if (!res.ok) {
  const problem = await res.json();

  if (problem.type === DOCUMENT_QUOTA_EXCEEDED) {
    console.error('Sin cupo disponible:', {
      confirmados: problem.consumoConfirmado,
      reservas: problem.reservasActivas,
      finPeriodo: problem.finPeriodo,
    });
    throw new CupoAgotadoError(problem);
  }

  if (problem.type?.includes('validation-error')) {
    for (const [campo, mensaje] of Object.entries(problem.errores ?? {})) {
      console.error(`Campo inválido: ${campo} → ${mensaje}`);
    }
    throw new ValidacionError(problem);
  }

  if (problem.type?.includes('invalid-enum-value')) {
    console.error(`Valor inválido en ${problem.campo}. Aceptados:`, problem.valoresAceptados);
    throw new EnumError(problem);
  }

  throw new ApiError(problem);
}

const { cdc } = await res.json();

Tabla rápida de tipos

StatusTipoAcción
400validation-errorRevisar errores por campo, corregir y reenviar
400invalid-enum-valueUsar uno de valoresAceptados
400invalid-formatCorregir el campo indicado en campo — mirá tipoEsperado y valorRecibido
401(texto plano)API key inválida/expirada. Rotá la credencial desde el panel
403document-quota-exceededLa emisión no se ejecutó. No reintentes en un loop; esperá a liberar capacidad, al siguiente período o cambiá de plan
403access-deniedEl usuario/key no tiene acceso a ese contribuyente
404*-not-foundRecurso inexistente. Verificar IDs
405method-not-allowedMétodo HTTP incorrecto para ese endpoint
409duplicate-*Ya existe. Usar el existente o cambiar identificador
409idempotency-in-progressEsperá Retry-After y repetí exactamente el mismo request con la misma clave
409idempotency-outcome-unknownResultado terminal indeterminado. Detenete: no reenvíes ni cambies la clave
409idempotency-key-expiredVenció el replay de 7 días. La clave sigue reservada y no puede reutilizarse
415unsupported-media-typeMandá Content-Type: application/json
422configuracion-incompletaFalta configuración de la cuenta (campo + accion dicen cuál). Reintentar no ayuda
422documento-electronico-generation-errorError de compliance SIFEN al generar el XML
422idempotency-key-reusedLa clave ya pertenece a otro payload u otra operación. No la recicles
503idempotency-upstream-unknownEsperá Retry-After y repetí exactamente el mismo request con la misma clave
503idempotency-unavailable / idempotency-fingerprint-unsupportedLa operación no se ejecutó. Conservá la clave y reintentá más tarde

Catálogo completo en Errores.

Rechazos SIFEN (asíncronos)

Después de emitir, el documento queda en PENDIENTE o EN_LOTE. Una vez procesado, SIFEN devuelve un código con la respuesta. Los más frecuentes en producción:

Código SIFENSignificadoCómo corregirlo
1108Fecha fin de vigencia del timbrado incorrecta (timbrado vencido o mal configurado)Renová o corregí la fechaFin del timbrado en SET y actualizalo en Sifende
1302Falta tipoContribuyente del receptor para B2BCompletar tipoContribuyente: "CONTRIBUYENTE"
1303Se informó tipoContribuyenteReceptor (D207) cuando el receptor es NO_CONTRIBUYENTESacá tipoContribuyenteReceptor; tipoContribuyente va siempre
1304Falta numeroDocumento (RUC) para receptor contribuyenteCompletar el RUC del receptor
1305Se informó el RUC del receptor (D206) cuando el receptor es NO_CONTRIBUYENTEnumeroDocumento no se elimina: es obligatorio y en B2C viaja como CI/pasaporte (D210). Verificá que no estés mandando datos de RUC/contribuyente para un receptor no contribuyente
1306RUC del receptor inexistente en Marangatu (RUC no registrado en SET)Confirmar el RUC con tu cliente
1309DV del RUC del receptor incorrectoVerificar digitoVerificador
2026CDC asociado no existe o no está aprobadoSolo emitir NCE/NDE sobre FE en estado APROBADO o APROBADO_OBSERVACION

Tabla completa: Rechazos SIFEN.

Cómo leer el rechazo

El campo mensajeRechazo del status response contiene el código y la descripción:

const resultado = await esperarResultadoSIFEN(cdc);

if (resultado.estado === 'RECHAZADO') {
  // Ej: "1108 - Timbrado vencido"
  const codigo = resultado.mensajeRechazo?.split(' ')[0];
  log.warn({ cdc, codigo, motivo: resultado.mensajeRechazo }, 'DE rechazado por SIFEN');
}

Cuándo reintentar (y cuándo no)

Situación¿Reintentar?Cómo
5xx en un GET (consulta)✅Backoff exponencial (1s, 2s, 4s…), máximo 3 intentos
Timeout, reset o conexión cortada antes de recibir una respuesta de un POST con clave✅Repetí exactamente el mismo request con la misma Idempotency-Key
409 idempotency-in-progress✅Esperá Retry-After y repetí el mismo request con la misma clave
503 idempotency-upstream-unknown✅Esperá Retry-After y repetí el mismo request con la misma clave
409 idempotency-outcome-unknown❌Detenete. El resultado es indeterminado y terminal; no reenvíes ni cambies de clave
409 idempotency-key-expired❌El replay venció y la clave sigue reservada; no la recicles
503 idempotency-unavailable / idempotency-fingerprint-unsupported✅No hubo efecto. Conservá la clave y reintentá cuando el contrato esté disponible
5xx en un POST sin clave⚠️Consultá el estado antes de reenviar — ver abajo
403 document-quota-exceeded❌ inmediatoNo hubo efecto. Reintentá solo cuando un rechazo libere capacidad, comience otro período o cambie el plan; las reservas no son consumo cobrado
401 / 403 access-denied❌Arreglá credenciales o permisos, no reintentes con la misma configuración
400 validation-error❌Corregí los datos antes de reenviar
422 configuracion-incompleta❌Completá lo que indica campo en el panel; reintentar no cambia nada
404 documento-electronico-not-found❌El CDC no existe; no aparecerá reintentando
Rechazo SIFEN⚠️Emití un DE nuevo con los datos corregidos. El original queda rechazado para siempre

Para el contrato completo —creación y persistencia de claves, decisiones de retry, diferencias entre las tres operaciones y reserva permanente— ver Idempotencia y Reintentos Seguros.

Un 5xx en un POST sin Idempotency-Key no significa que la operación no ocurrió. Si reenviás a ciegas y el primer request sí produjo un efecto, podés duplicar una emisión o un evento tributario.

El problem detail indica este riesgo con resultadoIndeterminado:

{
  "status": 500,
  "type": "https://sifende.com.py/docs/solucion-problemas/internal-error",
  "detail": "Ocurrió un error inesperado y el resultado de la operación es indeterminado...",
  "resultadoIndeterminado": true,
  "accion": "Consultá el estado del recurso antes de reenviar...",
  "traceId": "f2f0333f78bfb6a4184cb5a5374685a5"
}

Con resultadoIndeterminado: true, investigá el resultado antes de reenviar. Con false en una consulta GET, la operación no tocó datos y el reintento es seguro.

Recomendaciones de logging

En tu integración, logueá siempre:

  • cdc, para correlacionar con tu sistema.
  • estado y mensajeRechazo cuando consultes status.
  • type, title, status y detail del Problem Details ante errores.
  • El request body completo en errores 4xx (excepto la API key); facilita reproducir el problema.
log.error({
  cdc,
  estado: resultado.estado,
  mensajeRechazo: resultado.mensajeRechazo,
}, 'DE rechazado');

Próximos pasos

On this page