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:
APROBADOoAPROBADO_OBSERVACION. KuDE no se genera paraPENDIENTE,EN_LOTEniRECHAZADO. - Necesitás el CDC del documento.
- Aplica a los tipos soportados: FE, NCE, NDE.
El endpoint
GET /api/v1/documento-electronico/:cdc/kudePuede 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 headerLocation, que apunta a la misma ruta consoloConsulta=true, y esperá al menosRetry-Aftersegundos.500/503 application/problem+json: error técnico clasificado por causa.503 kude-unavailablesignifica 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
doneNo 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
| Status | Tipo | Causa | Solución |
|---|---|---|---|
| 404 | documento-electronico-not-found | CDC inexistente, ajeno o fuera de conservación física | Verificá el CDC y el contribuyente |
| 500 | kude-generation-error | Error técnico interno al preparar u obtener el PDF | No lo trates como problema del payload; contactá soporte con el traceId si persiste |
| 501 | kude-not-supported | Tipo de documento sin soporte de KuDE (ej: AFE 🚧) | Esperá a que se implemente. Solo FE/NCE/NDE actualmente |
| 503 | kude-unavailable | Indisponibilidad temporal identificada | Reintentá 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
Locationrecibido. Ya incluyesoloConsulta=truey conserva la ruta correcta. - No descargues KuDE en cada request del cliente. Guardalo en blob storage (S3, GCS) después de recibir
200 application/pdfy 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
- ¿Todavía no aprobaron tu DE? → Consultar Estado.
- Si tu cliente reporta problemas con el QR del KuDE, ver FAQ.
- Detalles de la API → Referencia: Descargar KuDE.