SIFENDE
Guías

Descargar KuDE

Cómo obtener el KuDE (comprobante PDF) de un documento aprobado para entregarlo al cliente.

El KuDE (Kuatia'i Documento Electrónico) es la representación gráfica del documento electrónico: el PDF imprimible o adjuntable por email que entregás al cliente. No es el documento legal en sí mismo, pero es la versión legible para humanos.

El documento legal es el XML firmado, no el KuDE. El cliente puede validar su factura escaneando el QR del KuDE, que apunta al portal SIFEN. No estás obligado a imprimirlo o enviarlo, pero sí es la forma estándar de entregarlo.

Requisitos

  • El documento debe estar registrado en SIFEN: APROBADO o APROBADO_OBSERVACION. KuDE no se genera para PENDIENTE, EN_LOTE ni RECHAZADO.
  • Necesitás el CDC del documento.
  • Aplica a los tipos soportados: FE, NCE, NDE.

El endpoint

GET /api/v1/documento-electronico/:cdc/kude

Puede responder:

  • 200 application/pdf: el body es el PDF. Guardalo o streamealo.
  • 202 application/json: el PDF todavía está en preparación. Seguí el header Location, que apunta a la misma ruta con soloConsulta=true, y esperá al menos Retry-After segundos.
  • 500/503 application/problem+json: error técnico clasificado por causa. 503 kude-unavailable significa indisponibilidad temporal identificada.

soloConsulta=true nunca reencola ni crea otra solicitud. Consultar repetidamente no repara por sí mismo; sólo observa el estado registrado.

Descargar y guardar a disco (cURL)

api_origin="https://api.sifende.com.py"
url="$api_origin/api/v1/documento-electronico/$CDC/kude"

while true; do
  status=$(curl -sS -D headers.txt -o respuesta.bin -w '%{http_code}' \
    "$url" \
    -H "Authorization: Bearer $SIFENDE_API_KEY")

  if [ "$status" = "200" ]; then
    mv respuesta.bin factura.pdf
    break
  fi

  if [ "$status" = "202" ]; then
    location=$(awk 'tolower($1)=="location:" {print $2}' headers.txt | tr -d '\r')
    url="$api_origin$location"
    sleep "$(awk 'tolower($1)=="retry-after:" {print $2}' headers.txt | tr -d '\r')"
    continue
  fi

  cat respuesta.bin >&2
  exit 1
done

No uses curl -o factura.pdf sin revisar el status: si la API responde 202, guardarías un JSON como si fuera PDF.

Descargar desde Node.js / TypeScript

Guardar el PDF en disco usando fs/promises sólo cuando la API devuelve 200 application/pdf:

import { writeFile } from 'node:fs/promises';

type ProblemDetail = {
  type: string;
  title: string;
  status: number;
  detail: string;
  traceId?: string;
};

async function esperar(ms: number): Promise<void> {
  await new Promise(resolve => setTimeout(resolve, ms));
}

async function descargarKuDE(cdc: string, destino: string): Promise<void> {
  let url = `https://api.sifende.com.py/api/v1/documento-electronico/${cdc}/kude`;

  for (;;) {
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.SIFENDE_API_KEY}` },
    });

    if (res.status === 200 && res.headers.get('content-type')?.includes('application/pdf')) {
      const buffer = Buffer.from(await res.arrayBuffer());
      await writeFile(destino, buffer);
      return;
    }

    if (res.status === 202) {
      const location = res.headers.get('location');
      if (!location) throw new Error('KuDE pendiente sin Location');
      await res.json();
      await esperar(Number(res.headers.get('retry-after') ?? '5') * 1000);
      url = new URL(location, url).toString();
      continue;
    }

    const problem = await res.json() as ProblemDetail;
    throw new Error(`No se pudo descargar KuDE (${problem.status}): ${problem.type}`);
  }
}

await descargarKuDE(
  '01800123451001001000000122026042710000000006',
  './kude/factura-001.pdf'
);

Stream a una respuesta HTTP (Express)

Si tu backend está sirviendo el KuDE al frontend o al cliente directamente, no streamees un 202 como PDF. Respondé 202 a tu propio cliente, o seguí el Location hasta obtener el 200.

import express from 'express';

const app = express();

app.get('/facturas/:cdc/kude', async (req, res) => {
  let url = `https://api.sifende.com.py/api/v1/documento-electronico/${req.params.cdc}/kude`;

  for (;;) {
    const upstream = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.SIFENDE_API_KEY}` },
    });

    if (upstream.status === 200 && upstream.headers.get('content-type')?.includes('application/pdf')) {
      res.setHeader('Content-Type', 'application/pdf');
      res.setHeader('Content-Disposition', `inline; filename="factura-${req.params.cdc}.pdf"`);
      const reader = upstream.body!.getReader();
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        res.write(value);
      }
      res.end();
      return;
    }

    if (upstream.status === 202) {
      const location = upstream.headers.get('location');
      if (!location) return res.status(502).json({ error: 'KuDE pendiente sin Location' });
      await upstream.json();
      await new Promise(resolve => setTimeout(resolve, Number(upstream.headers.get('retry-after') ?? '5') * 1000));
      url = new URL(location, url).toString();
      continue;
    }

    return res.status(upstream.status).json(await upstream.json());
  }
});

Usá Content-Disposition: inline para que se muestre embebido en el navegador, o attachment; filename="..." para forzar descarga.

Adjuntar el KuDE a un email al cliente

Adjuntá sólo bytes obtenidos con 200 application/pdf. Si el primer intento responde 202, seguí el Location; si responde 500 o 503, no envíes un correo sin adjunto y registrá el traceId para soporte.

import { readFile } from 'node:fs/promises';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);

async function obtenerPdfKuDE(cdc: string): Promise<Buffer> {
  const destino = `/tmp/${cdc}.pdf`;
  await descargarKuDE(cdc, destino);
  return readFile(destino);
}

async function enviarFacturaPorEmail(cdc: string, emailCliente: string) {
  const pdf = await obtenerPdfKuDE(cdc);

  await resend.emails.send({
    from: '[email protected]',
    to: emailCliente,
    subject: `Tu factura electrónica`,
    html: '<p>Adjuntamos tu factura electrónica. Podés validarla escaneando el QR.</p>',
    attachments: [
      { filename: `factura-${cdc}.pdf`, content: pdf },
    ],
  });
}

Errores frecuentes

StatusTipoCausaSolución
404documento-electronico-not-foundCDC inexistente, ajeno o fuera de conservación físicaVerificá el CDC y el contribuyente
500kude-generation-errorError técnico interno al preparar u obtener el PDFNo lo trates como problema del payload; contactá soporte con el traceId si persiste
501kude-not-supportedTipo de documento sin soporte de KuDE (ej: AFE 🚧)Esperá a que se implemente. Solo FE/NCE/NDE actualmente
503kude-unavailableIndisponibilidad temporal identificadaReintentá más tarde si corresponde; ver KuDE temporalmente no disponible

Buenas prácticas

  • No guardes un 202 como PDF. Es JSON de estado pendiente.
  • Seguí el Location recibido. Ya incluye soloConsulta=true y conserva la ruta correcta.
  • No descargues KuDE en cada request del cliente. Guardalo en blob storage (S3, GCS) después de recibir 200 application/pdf y serví desde ahí.
  • No mostrés KuDE de documentos no aprobados. Pueden cambiar, y al cliente le confunde.
  • Cacheá el KuDE: una vez que el DE quedó registrado en SIFEN y el PDF existe, el contenido no cambia.
  • Si el QR del KuDE no resuelve en SIFEN, verificá que estés en el ambiente correcto (test vs producción).

Próximos pasos

On this page