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. En una NRE, el KuDE debe acompañar la mercadería durante el traslado.
El KuDE de SANDBOX lleva el rótulo SANDBOX — SIN VALIDEZ FISCAL. Descargalo con una clave del mismo contribuyente y ambiente; el CDC y la ruta de descarga se usan igual que en los otros ambientes.
Requisitos
- Para NRE, la descarga requiere
APROBADOoAPROBADO_OBSERVACION. Para los demás tipos, esperá la aprobación antes de entregar el comprobante al cliente. - Necesitás el CDC del documento.
- Aplica a los tipos soportados: FE, AFE, NCE, NDE y NRE.
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 que el PDF no se pudo obtener en este intento.
Seguí el Location; consultar no acelera la preparación.
Si sigue en 202 después de 2 minutos, repetí una vez la descarga sin soloConsulta: el Location sólo consulta y no vuelve a pedir la preparación. Si después de otros 2 minutos sigue pendiente, dejá de consultar y contactá a soporte con el CDC.
Descargar y guardar a disco (cURL)
api_origin="https://api.sifende.com.py"
url_inicial="$api_origin/api/v1/documento-electronico/$CDC/kude"
url="$url_inicial"
limite=$((SECONDS + 120))
volvio_a_pedir=0
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')
sleep "$(awk 'tolower($1)=="retry-after:" {print $2}' headers.txt | tr -d '\r')"
if [ "$SECONDS" -lt "$limite" ]; then
url="$api_origin$location"
elif [ "$volvio_a_pedir" = 0 ]; then
volvio_a_pedir=1
url="$url_inicial"
limite=$((SECONDS + 120))
else
echo "KuDE pendiente por más de 4 minutos; contactá a soporte" >&2
exit 1
fi
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> {
const urlInicial = `https://api.sifende.com.py/api/v1/documento-electronico/${cdc}/kude`;
let url = urlInicial;
let limite = Date.now() + 2 * 60_000;
let volvioAPedir = false;
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);
if (Date.now() < limite) {
url = new URL(location, url).toString();
} else if (!volvioAPedir) {
volvioAPedir = true;
url = urlInicial;
limite = Date.now() + 2 * 60_000;
} else {
throw new Error('KuDE pendiente por más de 4 minutos; contactá a soporte');
}
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 servidor 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`;
const limite = Date.now() + 2 * 60_000;
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();
if (Date.now() >= limite) return res.status(503).json({ error: 'KuDE todavía en preparación; reintentá más tarde' });
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.
Errores frecuentes
| Status | Tipo | Causa | Solución |
|---|---|---|---|
| 403 | — (sin Problem Details) | La NRE no está en APROBADO ni APROBADO_OBSERVACION | Consultá su estado y esperá la aprobación sólo si sigue en proceso |
| 404 | documento-electronico-not-found | Documento no encontrado o no accesible para esta clave | 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 | El tipo de documento no admite KuDE | Revisá el tipo; FE, AFE, NCE, NDE y NRE admiten KuDE |
| 503 | kude-unavailable | No se pudo obtener el PDF en este intento | Reintentá más tarde si corresponde; ver KuDE 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. - Poné un límite a la espera. Si sigue pendiente después de 2 minutos, volvé a pedir la descarga sin
soloConsultauna sola vez. - No descargues KuDE en cada solicitud del cliente. Conservá una copia después de recibir
200 application/pdf. - 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.