Facturación electrónica Paraguay (SIFEN)
Emití facturas, notas de crédito y de débito electrónicas ante la DNIT/SET de Paraguay de forma programática, con el mismo API token que el resto de la API.
Introducción
Los contribuyentes de Paraguay facturan ante la DNIT/SET a través de SIFEN (e-Kuatia). YoFacturo arma el Documento Electrónico (DE), lo firma con tu propio certificado, lo transmite y guarda el KuDE (la representación impresa con el QR). Todo se maneja con un API token (yf_live_* o yf_test_*), igual que la facturación AFIP: Autenticación.
Las rutas de este recurso viven bajo https://api.yo-facturo.com/api/v1/bill/sifen. La emisión no es una ruta nueva: sale de POST /sales/ (o POST /sales/generate_invoice/{sale_id}/ sobre una venta existente) y es asíncrona, igual que AFIP. Las rutas de /bill/sifen/* sirven para leer, consultar y operar los documentos ya emitidos, y para el onboarding.
Documentos soportados por la API: FE (factura electrónica, iTiDE 1), NCE (nota de crédito, 5) y NDE (nota de débito, 6). Moneda: guaraníes (PYG, sin decimales). Alícuotas de IVA: 10 %, 5 % y exento (0).
Importante. Si tu cuenta factura con SIFEN, las rutas de AFIP /bill/receipts/* responden 409 con error_code: use_sifen_routes: no se mezclan. Emití con POST /sales/ y las notas con POST /sales/{sale_id}/credit_note/.
Scopes
| Scope | Para qué | Rutas |
|---|---|---|
invoices:read | Leer documentos, XML, eventos, KuDE, catálogos, tipos, numeración, RUC, lotes y link público | GET /bill/sifen/documents/…, /catalogs/, /types/, /lotes/{protocolo}/, /numbering/, /ruc/{ruc}/, POST /validate-type/, GET …/kude/ |
invoices:write | Operar documentos: emitir una factura sin venta, consulta en vivo, anular, nota de crédito/débito, inutilización, email, prueba de conectividad | POST /bill/sifen/documents/, POST …/consulta/, /void/, /debit-note/, /inutilizaciones/, /send-email/, /diagnose/, POST /sales/{id}/credit_note/, POST /sales/generate_invoice/{id}/ |
accounting:read / accounting:write | Configuración: certificados, cajas, ambiente, numeración inicial, readiness, verificación de caja | /bill/tax_info/taxpayer/sifen/…, /sales-points/, /bill/sifen/readiness/, /environment/, /numbering/seed/, /verify-caja/ |
sales:write | Crear la venta que dispara la factura y calcular sus totales | POST /sales/, POST /sales/totals-preview/ |
received_invoices:write | Eventos del receptor sobre DE que recibiste | POST /received_invoices/sifen/events/ |
Además del scope, el dueño del token necesita el permiso de panel equivalente (por ejemplo administration:invoices:delete para anular o inutilizar): si le falta, la ruta responde 403. Rate limit general: 10/s, 100/min, 3000/h, 20000/día por cuenta; algunas rutas tienen un límite propio (se indica en cada una).
Onboarding: dejar la cuenta lista para emitir
Antes del primer documento hay que cargar los datos fiscales, el certificado de firma y la numeración de las cajas. Todo se puede hacer desde el panel (Configuración → Legal → pestaña Paraguay) o por API. GET /bill/sifen/readiness/ te dice en cualquier momento qué falta.
1. Datos fiscales de Paraguay
El RUC va sin dígito verificador en tax_id y el DV en dv. El timbrado (8 dígitos) y su vigencia salen de Marangatu. establishment y expedition_points son los códigos de 3 dígitos habilitados en tu timbrado. POST y PUT hacen lo mismo (crean o actualizan; la respuesta es 201 con billing_system y document_id) y tienen un límite de 5 cambios por día. El PKCS#12 y el CSC no viajan acá.
curl -X POST https://api.yo-facturo.com/api/v1/bill/tax_info/taxpayer/sifen/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
-d '{
"tax_id": "80069563", "dv": "1",
"business_name": "Mi Empresa S.A.",
"fiscal_address": "Av. Mariscal López 1234",
"email": "[email protected]", "phone": "0981123456",
"timbrado": "12345678",
"timbrado_start_date": "2026-01-01T00:00:00Z",
"timbrado_end_date": "2027-01-01T00:00:00Z",
"establishment": "001", "expedition_points": ["001", "002"],
"economic_activities": [{"code": "47111", "description": "Venta al por menor en supermercados"}],
"department_code": 1, "department_desc": "CAPITAL",
"city_code": 1, "city_desc": "ASUNCION", "house_number": 1234
}'Los códigos de departamento, distrito y ciudad son los de la tabla oficial de la DNIT: SIFEN exige que código y descripción coincidan (1255/1258/1259). La respuesta 400 dice qué campo falló. Si tu cuenta maneja más de un sistema fiscal (por ejemplo Argentina y Paraguay), el sistema activo se cambia con PATCH /bill/tax_info/taxpayer/active-system/.
2. Certificado F1 y CSC
SIFEN firma con el certificado digital F1 (software, PKCS#12 `.p12`/`.pfx`) emitido por un PSC habilitado a nombre del contribuyente: no se aceptan certificados en hardware (token). Además necesitás el CSC (Código de Seguridad del Contribuyente) de Marangatu: un csc_id de 1 a 4 dígitos y un csc de exactamente 32 caracteres alfanuméricos. Hay un juego por ambiente (test y prod); empezá por test.
curl -X POST https://api.yo-facturo.com/api/v1/bill/tax_info/taxpayer/sifen/certificates/test/ \ -H "X-API-Key: $TOKEN" \ -F "[email protected]" -F "password=$P12_PASSWORD" \ -F "csc_id=0001" -F "csc=ABCDEFGHIJKLMNOPQRSTUVWXYZ012345" # 201 → {"success": true, "data": {"environment": "test", "configured": true, "not_after": "...", "fingerprint_sha256": "...", "has_csc": true}}
multipart/form-data, archivo de hasta 5 MB. El certificado se valida en memoria (RSA de 2048 bits o más,clientAuth, clave que corresponde, vigente) y se guarda cifrado en Vault: ni el.p12, ni la contraseña, ni el CSC se vuelven a mostrar ni se loguean.POSTfalla con 409 si ya hay un certificado en ese ambiente: usáPUTpara reemplazarlo (el CSC es opcional enPUT, se conserva el anterior).GET /bill/tax_info/taxpayer/sifen/certificates/(o…/{ambiente}/) devuelve solo metadatos: sujeto, vencimiento, huella,expiring_soon(30 días o menos). Se alerta antes del vencimiento.- Límite: 10 subidas por hora. Los errores de validación vuelven como 400 con el motivo en
error(certificado vencido, clave débil, CSC inválido…).
3. Cajas: establecimiento y punto de expedición
Cada caja (punto de venta) numera sus documentos con su propio par establecimiento-puntoExpedicion (3 dígitos cada uno, habilitados en el paso 1). Una caja SIFEN lleva billing_system: "sifen". Las ventas de la caja salen con su numeración; una venta sin caja usa los valores por defecto de los datos fiscales.
curl -X POST https://api.yo-facturo.com/api/v1/sales-points/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "Caja 1", "billing_system": "sifen",
"sifen_establishment": "001", "sifen_expedition_point": "001"}'Si migrás desde otro sistema y la numeración ya está avanzada, fijá el último número usado: POST /bill/sifen/numbering/seed/ con {"last_used": 120, "est": "001", "punExp": "001"}. Solo avanza (409 not_upward si baja), nunca por debajo de un documento ya emitido (409 higher_number_in_use) y acepta dry_run: true. El próximo documento sale con last_used + 1; GET /bill/sifen/numbering/ muestra el último y el próximo.
4. Probar en test, validar y pasar a producción
environment del contribuyente es test por defecto: los documentos van a sifen-test, no tienen valor fiscal y el nombre del emisor sale con la leyenda de prueba. Comprobá todo con la readiness:
curl https://api.yo-facturo.com/api/v1/bill/sifen/readiness/ -H "X-API-Key: $TOKEN"
# {"success": true, "data": {"can_invoice": false, "environment": "test",
# "blockers": [{"code": "csc_missing", "message": "Falta el CSC (IdCSC + código) del ambiente test.",
# "action_url": "/settings/legal", "api_action": "PUT /api/v1/bill/tax_info/taxpayer/sifen/certificates/test/"}],
# "warnings": [], "checks": {...}}}can_invoice es true cuando no quedan blockers (identidad, geografía, timbrado vigente, certificado y CSC del ambiente activo, al menos una caja con numeración). Los warnings avisan de timbrado o certificado a 30 días de vencer. Es una lectura local y barata: no llama a SIFEN. Para probar el certificado contra SIFEN, consultá tu propio RUC con GET /bill/sifen/ruc/{ruc}/.
Cuando tengas el certificado y el CSC de producción cargados, cambiá de ambiente con POST /bill/sifen/environment/ y {"environment": "prod"}. Se rechaza con 409 si el ambiente destino no puede firmar (certificate_missing, csc_missing, certificate_expired, certificate_not_yet_valid) y un token yf_test_* nunca puede pasar a prod (sandbox_prod_forbidden). El cambio queda auditado.
Emitir una factura
La factura se pide al crear la venta. En un tenant o caja SIFEN no hay letra A/B/C: mandá request_fiscal_document: true (o generate_invoice: true, equivalente). Cada renglón declara su alícuota en metadata.vat_rate (o la toma del producto: sale_iva_rate, o sifen_vat_rate si el producto lo define para Paraguay): 10, 5 o 0 (exento). Contrato de la alícuota: con request_fiscal_document / generate_invoice en una cuenta SIFEN, cualquier alícuota que no sea 10, 5 o 0 (un renglón sin alícuota cuenta como 21 %) responde 400 (success: false, mensaje "SIFEN solo admite IVA 10%, 5% o exento (SIFEN 1908)…") antes de guardar la venta: no se crea nada y no hay que reintentar con el mismo cuerpo. La misma regla rige con un token yf_live_* y con el JWT del panel. Una venta sin pedido de factura se guarda igual; si después pedís su factura con POST /sales/generate_invoice/{id}/ se vuelve a validar antes de reservar un número. El total del DE coincide con el total de la venta: el servidor calcula todo, no mandes importes de IVA.
curl -X POST https://api.yo-facturo.com/api/v1/sales/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-2026-10-07-0001" \
-d '{
"customer_id": "665f0c2a9d1e4b0012ab34cd",
"items": [
{"name": "Yerba mate 1 kg", "quantity": 2, "price": 22000, "metadata": {"vat_rate": 10}},
{"name": "Libro", "quantity": 1, "price": 55000, "metadata": {"vat_rate": 5}}
],
"payment_method": "cash",
"sales_point_id": "665f0c2a9d1e4b0012ab34ce",
"request_fiscal_document": true
}'billing_system(opcional): solo en cuentas con más de un sistema fiscal, marca la venta con el sistema que debe emitirla ("sifen"). Si se omite rige el de la caja o el sistema activo del contribuyente. Con un solo sistema configurado no hace falta.sales_point_id: la caja define el establecimiento y el punto de expedición.customer_idcon RUC: el receptor se emite como contribuyente (se valida con siConsRUC, caché de 24 h); sin RUC, como no contribuyente identificado por cédula; sin ningún documento, como innominado, solo mientras el total sea menor a 60.000.000 Gs (SIFEN 1321).Idempotency-Key: reintentar con la misma clave reproduce la respuesta y no duplica la venta ni el documento (además hay un único documento vivo por venta).
Calcular los totales antes de vender (totals-preview)
POST /sales/totals-preview/ (scope sales:write, mismo cuerpo que POST /sales/) responde los totales con que se guardaría la venta, sin guardarla. En una cuenta (o caja) que resuelve a SIFEN, la respuesta agrega el bloque sifen con lo que declararía el DE: los importes por alícuota en guaraníes, el redondeo de 50 Gs (dRedon) y el total del documento. Se calcula con las mismas funciones que firma el emisor, así que la vista previa coincide con el DE; el cliente no calcula nada. Una cuenta AFIP recibe exactamente la respuesta de siempre (sin el bloque sifen).
{"success": true, "data": {
"total": 19000.0, "vat_breakdown": [...],
"sifen": {
"system": "sifen", "valid": true, "currency": "PYG",
"buckets": {"10": {"gross": 11000, "base": 10000, "iva": 1000},
"5": {"gross": 5000, "base": 4762, "iva": 238},
"exento": {"gross": 3000, "base": 3000, "iva": 0}},
"total_operacion": 19000, "redondeo": 0, "total_iva": 1238, "total_general": 19000
}}}Si el carrito trae una alícuota que SIFEN no declara, la vista previa no falla: sifen.valid es false y sifen.error explica el motivo (el mismo que devolvería la venta con 400).
Emitir una factura sin venta
Para facturar un servicio o un cobro que no pasa por el POS ni por una venta, POST /bill/sifen/documents/ (scope invoices:write, Idempotency-Key, 30/min) emite una FE (iTiDE 1) directamente: usa la misma numeración atómica, firma, persistencia y transmisión en segundo plano que la factura de una venta. El AFE (4) y la NRE (7) no se emiten por esta ruta (type_not_issuable); las notas usan /debit-note/ y POST /sales/{id}/credit_note/.
curl -X POST https://api.yo-facturo.com/api/v1/bill/sifen/documents/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: honorarios-2026-10" \
-d '{
"tipo_de": 1,
"receptor": {"document_type": "ruc", "document_number": "80012345-6", "name": "Cliente SA", "email": "[email protected]"},
"items": [
{"descripcion": "Honorarios octubre", "cantidad": 1, "importe": 1100000, "tasa_iva": 10},
{"descripcion": "Libro", "cantidad": 2, "precio_unitario": 27500, "tasa_iva": 5}
],
"condicion": "contado", "establecimiento": "001", "punto_expedicion": "002",
"client_reference": "honorarios-0001"
}'- Importes: cada renglón lleva
importe(total bruto del renglón) oprecio_unitarioxcantidad, siempre en guaraníes enteros y con el IVA incluido, mástasa_iva(10, 5 o 0; default 10). El servidor calcula base, IVA, redondeo y total: no mandes totales. Un renglón cuyo importe no se divide en unidades iguales se rechaza (mandácantidad: 1y el total comoimporte). Hasta 50 renglones. - Receptor:
document_typeruc(se valida con siConsRUC),ci(cédula) onone(innominado, solo bajo 60.000.000 Gs);document_numberes obligatorio salvonone. - Condición:
contado(conpago_tipo, default 1 efectivo; los pagos con tarjeta o cheque necesitan datos que esta ruta no lleva) ocredito(plazo_dias, default 30, máx. 365). - Caja:
sales_point_id(una caja SIFEN con su numeración) oestablecimiento+punto_expedicion(un par que el contribuyente tiene configurado, 400caja_not_configuredsi no); sin ninguno rige el establecimiento y punto por defecto. Verificá la caja antes conPOST /bill/sifen/verify-caja/. - Idempotencia: el
Idempotency-Keyreproduce la respuesta. Ademásclient_reference(hasta 64 caracteres) devuelve el mismo documento (replayed: true, mismo CDC) si ya se emitió uno con esa referencia, sin consumir otro número; si el primero sigue emitiéndose responde 409issue_in_progress. - Respuesta
201:cdc,number,status: "signed",total,buckets,lines,qr_urlylinks. Es asíncrona como la factura de una venta: seguila conGET /bill/sifen/documents/{cdc}/(kind: "standalone", sinsale_id) o con los webhooks. Un tokenyf_test_*responde 409.
La respuesta no trae el CDC: seguila
El 201 confirma la venta. El documento se firma y se transmite en segundo plano, así que el CDC todavía no está en la respuesta. Tenés dos formas de enterarte:
- Polling: leé
GET /sales/{id}/y mirádata.fiscalhasta quefiscal.statusseaapproved(orejected). Con el CDC también podés usarGET /bill/sifen/documents/{cdc}/. - Webhook: suscribite a
invoice.authorized,invoice.rejectedeinvoice.cancelled; el cuerpo traesystem: "sifen", elcdcy el estado (ver más abajo).
GET /api/v1/sales/665f0d1e9d1e4b0012ab3501/
{
"success": true,
"data": {
"id": "665f0d1e9d1e4b0012ab3501",
"total": 99000,
"fiscal": {
"system": "sifen",
"cdc": "01800695631001001000000112026100710000000013",
"status": "approved",
"number": "001-001-0000001",
"qr_url": "https://ekuatia.set.gov.py/consultas/qr?nVersion=150&Id=0180...",
"timbrado": "12345678",
"inicio": "2026-01-01",
"kude_url": "/api/v1/sales/sifen/documents/01800695631001001000000112026100710000000013/kude/"
}
}
}Mientras no haya documento fiscal es null. Si SIFEN rechaza el documento, fiscal.status pasa a rejected y fiscal.rejection trae {code, message} de SIFEN; el detalle completo de todas las respuestas está en sifen_messages del documento.
Estados de un documento
| Estado | Qué significa | Qué hacer |
|---|---|---|
reserved, signed, sending | Número reservado, firmado y en camino | Esperar |
sent_sync, sent_lote, in_processing | Enviado; SIFEN lo está procesando (los lotes pueden tardar minutos u horas) | Esperar el webhook o consultar |
approved, approved_obs | SIFEN lo aprobó (approved_obs = con observaciones) | Listo: el KuDE es válido |
rejected | SIFEN lo rechazó | Leer sifen_messages, corregir la causa y reemitir con POST /sales/generate_invoice/{sale_id}/ (el documento rechazado libera la venta) |
unknown | Se perdió la respuesta de SIFEN: nunca se reenvía a ciegas | POST /bill/sifen/documents/{cdc}/consulta/ |
manual | Problema de certificado o TLS que requiere una acción | Revisar readiness y el certificado |
stale | Firmado hace más de 72 h sin poder transmitirse | Reemitir |
cancelled, inutilized | Anulado por un evento | — |
Ante una caída de SIFEN la venta no se pierde: el documento queda firmado y en cola, y se transmite apenas SIFEN responde (la validación es posterior, hasta 72 h). El KuDE con el CDC y el QR ya se puede imprimir.
KuDE: la representación impresa
El KuDE se arma desde el XML firmado, con los montos exactamente como se firmaron (nada se recalcula). La API entrega su view-model JSON; el panel lo dibuja en A4, en cinta y en cinta resumen (/print/sifen/{cdc}/kude).
| Ruta | Para qué |
|---|---|
GET /sales/{sale_id}/sifen/kude/ | KuDE de la factura vigente de una venta |
GET /sales/sifen/documents/{cdc}/kude/ | KuDE de cualquier documento por CDC (FE, NCE, NDE) |
POST /bill/sifen/documents/{cdc}/public-link/ | Link firmado sin login: {"expires_in_seconds": 604800} (60 s a 30 días, default 7 días); api_url devuelve el JSON del KuDE |
POST /bill/sifen/documents/{cdc}/send-email/ | Envía por email el link del KuDE ({"email": "…"}, por defecto el del cliente); 20 cada 5 minutos |
GET /bill/sifen/documents/{cdc}/xml/ | El XML firmado (application/xml) tal como se envió |
Responde 404 si la venta no tiene documento SIFEN y 409 si todavía no hay XML firmado. El link público se rechaza para documentos rechazados, stale, inutilizados o reservados. El QR es el dCarQR firmado, armado con el CSC en el servidor.
Notas de crédito y de débito
Nota de crédito electrónica (NCE)
POST /sales/{sale_id}/credit_note/ emite una NCE (iTiDE 5) asociada a la factura de la venta. En SIFEN las `lineas` son obligatorias: el índice (desde 0) del renglón de la factura y la cantidad a acreditar. Reglas: la factura debe estar aprobada (409 si no), un renglón no se acredita dos veces y la suma de las notas vivas no puede superar el total de la factura (SIFEN 2417). Motivo por defecto: 2 (devolución). Los efectos son los de AFIP: la venta pasa a acreditada si se acredita todo, restore_stock: true devuelve stock y las ventas a cuenta corriente acreditan la cuenta.
curl -X POST https://api.yo-facturo.com/api/v1/sales/665f0d1e9d1e4b0012ab3501/credit_note/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: nce-2026-10-07-0001" \
-d '{"reason": "Devolución de mercadería", "lineas": [{"linea": 0, "cantidad": 1}], "restore_stock": true}'POST /sales/{sale_id}/credit_note/preview/ devuelve el plan (total, lines, buckets por alícuota, fe_cdc) sin emitir ni tomar número. La clave de idempotencia puede ir en Idempotency-Key o X-Idempotency-Key (hasta 128 caracteres).
Nota de débito electrónica (NDE)
POST /bill/sifen/documents/{cdc}/debit-note/ (con {cdc} = el de la FE aprobada) emite una NDE (iTiDE 6) para cobrar un ajuste. Cada ítem lleva descripcion, importe bruto en guaraníes enteros, tasa_iva (10, 5 o 0) y opcional cantidad; motivo es el iMotEmi (1 a 8, default 8 ajuste de precio). No hay topes de monto, como en la nota de débito de AFIP. En una venta a cuenta corriente, el débito se registra cuando SIFEN aprueba la nota.
curl -X POST https://api.yo-facturo.com/api/v1/bill/sifen/documents/$CDC/debit-note/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: nde-0001" \
-d '{"items": [{"descripcion": "Ajuste de precio", "importe": 11000, "tasa_iva": 10}],
"motivo": 8, "reason": "Ajuste por diferencia de precio"}'Hay un …/debit-note/preview/ de solo lectura. Los motivos y alícuotas válidos están en GET /bill/sifen/catalogs/note_motives/ y …/iva_rates/.
Anular: cancelación, nota de crédito o inutilización
POST /bill/sifen/documents/{cdc}/void/ con {"reason": "…"} (5 a 500 caracteres) elige el camino correcto según el estado del documento. data.action dice qué se hizo:
| Situación | action | Qué ocurre |
|---|---|---|
| FE aprobada hace menos de 48 h | cancelacion | Evento de cancelación ante SIFEN; el documento pasa a cancelled |
| FE aprobada hace 48 h o más | nce | Se emite una nota de crédito por el saldo que quede |
| NCE / NDE aprobada hace menos de 168 h | cancelacion | Evento de cancelación. Fuera de esa ventana: 409, no se puede anular |
Número que nunca se aprobó (reservado, rechazado, stale) | inutilizacion | Se inutiliza ese número |
| Documento en vuelo (firmado, enviado…) | — | 409: esperá su estado final |
| Ya anulado | cancelled | Éxito sin reenviar nada (idempotente) |
Es idempotente (acepta Idempotency-Key), exige el permiso administration:invoices:delete en el dueño del token y está limitada a 20 por minuto. Las ventanas se cuentan desde la aprobación de SIFEN.
Inutilizar un rango de números
Para números que nunca se usaron (un talonario perdido, una secuencia salteada): POST /bill/sifen/inutilizaciones/. Un barrido diario ya inutiliza solo los números quemados que puede demostrar; esta ruta es para el resto.
curl -X POST https://api.yo-facturo.com/api/v1/bill/sifen/inutilizaciones/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: inu-0001" \
-d '{"tipo_de": 1, "establecimiento": "001", "punto_expedicion": "001",
"desde": 15, "hasta": 20, "reason": "Talonario extraviado"}'- Hasta 1000 números por pedido (400 si es más),
hasta >= desde,reasonde 5 a 500 caracteres;serieytimbradoson opcionales. - 409 si algún número ya tiene un documento utilizable (aprobado, enviado, firmado, cancelado o ya inutilizado) o todavía no fue entregado por el contador de numeración (inutilizarlo bloquearía el próximo documento).
- Idempotente por (secuencia, rango): repetir devuelve
replayed: truesin reenviar.
Eventos del receptor
Si tu cuenta recibió un DE de otro contribuyente, podés informar a SIFEN tu respuesta con POST /received_invoices/sifen/events/ (scope received_invoices:write, permiso accounting:received_invoices:edit). El evento se firma con tu certificado.
curl -X POST https://api.yo-facturo.com/api/v1/received_invoices/sifen/events/ \
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
-d '{"kind": "conformidad", "cdc": "01800695631001001000000112026100710000000013", "total": true}'kind:notificacion(recepción),conformidad,disconformidadodesconocimiento.reasonhasta 500 caracteres.- Ventana de 15 días desde la emisión (SIFEN 4152) y topes por tipo: una notificación, una disconformidad, un desconocimiento y hasta dos conformidades (4150/4200); no se da conformidad después de un desconocimiento (4156).
- Para corregir un evento ya aprobado mandá
correction_ofcon suevent_key.
API de documentos
Cada documento emitido (FE, NCE, NDE) es un recurso identificado por su CDC de 44 dígitos. Un CDC de otra cuenta responde 404.
| Método | Ruta | Descripción | Scope |
|---|---|---|---|
GET | /bill/sifen/documents/ | Lista con filtros tipo_de, status (varios, separados por coma), date_from, date_to, sale_id, cdc, caja (001-002), est, punExp; limit (máx. 200) y paginación por cursor | invoices:read |
POST | /bill/sifen/documents/ | Emite una FE sin venta (ver "Emitir una factura sin venta"); Idempotency-Key, 30/min | invoices:write |
GET | /bill/sifen/documents/{cdc}/ | Estado, número, timbrado, totales, QR, protocolo, sifen_messages y eventos | invoices:read |
GET | /bill/sifen/documents/{cdc}/xml/ | XML firmado | invoices:read |
GET | /bill/sifen/documents/{cdc}/events/ | Cancelación, eventos del receptor e inutilización que lo cubrió | invoices:read |
POST | /bill/sifen/documents/{cdc}/consulta/ | Consulta en vivo a SIFEN (siConsDE) y actualiza el estado local; 30/min; 503 si SIFEN no responde (el estado no cambia) | invoices:write |
POST | /bill/sifen/documents/{cdc}/void/ | Anular (cancelación, NCE o inutilización) | invoices:write |
POST | /bill/sifen/documents/{cdc}/debit-note/[preview/] | Nota de débito | invoices:write / read |
POST | /bill/sifen/inutilizaciones/ | Inutilizar un rango | invoices:write |
GET | /bill/sifen/catalogs/[{name}/] | Códigos válidos: tipos de documento, motivos de nota, tipos de documento del receptor, afectación de IVA, alícuotas, formas de pago | invoices:read |
GET | /bill/sifen/ruc/{ruc}/ | Razón social, estado y DV de un RUC (siConsRUC, caché 24 h, 30/min). Un DV errado da 400 dv_mismatch sin llamar a SIFEN | invoices:read |
GET | /bill/sifen/numbering/ | Último y próximo número de una secuencia | invoices:read |
GET /api/v1/bill/sifen/documents/01800695631001001000000112026100710000000013/
{
"success": true,
"data": {
"cdc": "01800695631001001000000112026100710000000013",
"tipo_de": 1, "tipo": "FE", "kind": "sale",
"status": "rejected",
"number": "001-001-0000001",
"total": "99000", "currency": "PYG", "environment": "test",
"sale_id": "665f0d1e9d1e4b0012ab3501",
"sifen_messages": [
{"kind": "recibe_de", "code": "<dCodRes>", "message": "<dMsgRes tal como lo devolvió SIFEN>", "at": "2026-10-07T14:03:11Z"}
],
"events": [],
"links": {"self": "/api/v1/bill/sifen/documents/0180…/", "xml": "…", "kude": "…"}
}
}Verificar, diagnosticar y consultar en vivo
| Ruta | Para qué sirve |
|---|---|
POST /bill/sifen/verify-caja/ (accounting:write) | ¿Esta caja puede emitir ahora? Body {establecimiento, punto_expedicion | sales_point_id, tipo_de?, check_ruc?}. Responde valid y checks[] (cada uno con code, ok, severity error/warning/info, message y action): timbrado vigente, el par autorizado para el contribuyente, el certificado vigente y emitido al mismo RUC del contribuyente (certificate_ruc_mismatch), y el estado del RUC emisor en SIFEN (emitter_ruc_active / inactive / not_found; si SIFEN no responde queda en warning). check_ruc: false omite la llamada a SIFEN. actions lista qué corregir. |
POST /bill/sifen/diagnose/ (invoices:write, 10/min) | Prueba en vivo de conectividad con tu certificado: lo carga de Vault, hace el handshake mTLS contra el host de tu ambiente y consulta tu propio RUC. Responde 200 con ok, environment, host, steps[] (certificate, tls_handshake, siConsRUC, cada uno con ok y latency_ms), tls, auth, ruc, latency_ms y actions si algo falló (certificado faltante, rechazado, SIFEN caído, 0501 sin permiso de consulta de RUC). Un fallo es un resultado, no un error HTTP. Un token yf_test_* usa siempre sifen-test. |
GET /bill/sifen/lotes/{protocolo}/ (invoices:read, 30/min) | Consulta en vivo (siConsLoteDE) de un lote que tu cuenta envió (el lote_protocol aparece en el documento). Solo lectura: state (in_process, concluded, expired), el código de SIFEN y el resultado de cada documento junto a su estado local. 404 lote_not_found si el protocolo no es de tu cuenta (no se llama a SIFEN), 400 invalid_protocol, 503 sifen_unreachable. |
GET /bill/sifen/types/ (invoices:read) | Tipos de documento (iTiDE) y cuáles emite la API, con la ruta de cada uno (contexts): FE (1) por venta o sin venta, NCE (5), NDE (6). AFE (4) y NRE (7) figuran con issuable_via_api: false. |
POST /bill/sifen/validate-type/ (invoices:read) | {tipo_de, context?} con context sale, standalone, credit_note o debit_note. Responde valid, issuable y reason cuando no se puede (misma semántica que validate-type de AFIP). |
Webhooks
Los eventos invoice.authorized, invoice.rejected e invoice.cancelled salen por el mismo despachador y la misma cola con reintentos que los de AFIP (Webhooks salientes). El cuerpo trae system: "sifen", así que un mismo endpoint distingue ambos sistemas. Cada transición se entrega una sola vez (dedupe por <cdc>:<evento>).
{
"system": "sifen",
"event": "invoice.authorized",
"cdc": "01800695631001001000000112026100710000000013",
"number": "001-001-0000001",
"status": "approved",
"sale_id": "665f0d1e9d1e4b0012ab3501",
"tipo_de": 1, "kind": "sale", "fe_cdc": null,
"environment": "prod", "total": "99000",
"qr_url": "https://ekuatia.set.gov.py/consultas/qr?...",
"protocol": "1234567890"
}invoice.rejected agrega error con el código y el mensaje de SIFEN en lugar de protocol. invoice.cancelled se emite cuando SIFEN aprueba un evento de cancelación. El webhook se dispara después de confirmar la transición, nunca antes.
Tokens de prueba (sandbox)
- Un token
yf_test_*lee todo contra su base sandbox aislada, pero nunca transmite a SIFEN:consulta,void,debit-note(emisión),inutilizacionesyPOST /bill/sifen/documents/responden 409;diagnose,verify-cajaylotesconsultan siempre sifen-test. El certificado en Vault es de tu cuenta real, por eso no se usa desde el sandbox. - El ambiente efectivo de un token de prueba es siempre
test(sifen-test): la readiness, la consulta de RUC y la emisión se evalúan contratest, y no puede cambiar aprod. - Para probar el ciclo completo (emitir, aprobar, anular) usá un token
yf_live_*con el contribuyente enenvironment: test: los documentos van a sifen-test, sin valor fiscal. POST /sandbox/reset/vacía la base sandbox (Sandbox).
Errores
Toda respuesta de error tiene la forma {"success": false, "error": "…", "code": "…"}; las rutas de operación de SIFEN agregan error_code, un motivo estable que podés usar en un switch. Los códigos HTTP siguen la convención general (Errores): 400 validación, 401/403 autenticación y permisos, 404 no existe (o es de otra cuenta), 409 conflicto de estado, 429 límite, 503 SIFEN no disponible.
error_code de las rutas de operación
| HTTP | error_code | Cuándo |
|---|---|---|
| 409 | use_sifen_routes | Llamaste una ruta AFIP /bill/receipts/* en un tenant SIFEN |
| 409 / 404 / 400 | tax_info_missing | No cargaste los datos fiscales de Paraguay (el código HTTP depende de la ruta) |
| 409 | certificate_missing · csc_missing · certificate_expired · certificate_not_yet_valid | El ambiente no puede firmar (cambio de ambiente, consulta de RUC) |
| 409 | sandbox_prod_forbidden | Un token yf_test_* pidió pasar a prod |
| 409 | not_upward · higher_number_in_use · serie_mismatch · counter_moved | La numeración solo avanza, nunca por debajo de un documento emitido |
| 400 | dv_mismatch · invalid_ruc | RUC mal formado o DV incorrecto (se rechaza sin llamar a SIFEN) |
| 404 | ruc_not_found | SIFEN no conoce ese RUC |
| 503 | sifen_unreachable · email_not_sent · signing_secret_unavailable | SIFEN, el correo o el firmador de links no están disponibles: reintentá |
| 400 / 401 / 409 | invalid_expiry · invalid_token · not_shareable · kude_unavailable | Link público o email del KuDE |
| 400 | type_not_issuable · invalid_tipo_de · invalid_items · invalid_vat_rate · invalid_receptor · invalid_caja · caja_not_configured | POST /bill/sifen/documents/: el cuerpo no se puede emitir; no se reservó ningún número |
| 409 | issue_in_progress | Una emisión con el mismo client_reference sigue en curso: reintentá en un momento |
| 404 / 400 | lote_not_found · invalid_protocol | GET /bill/sifen/lotes/{protocolo}/ |
| 400 | invalid_context | POST /bill/sifen/validate-type/ con un context desconocido |
Los errores de subida de certificado vuelven como 400 con el motivo en error (sin error_code).
Códigos de SIFEN (dCodRes) en la respuesta
Los códigos y mensajes de SIFEN no se traducen ni se ocultan: llegan tal cual. Se leen en sifen_messages[] de GET /bill/sifen/documents/{cdc}/ (cada dCodRes/dMsgRes con su kind y fecha), en fiscal.rejection de la venta, en el campo error del webhook invoice.rejected y en sifen_code/sifen_message de cada evento. Los que más vas a ver:
| Código | Significado |
|---|---|
0260 | DE aprobado (sincrónico) |
0300 · 0301 | Lote recibido / lote no encolado |
0360 · 0361 · 0362 · 0363 · 0364 | Consulta de lote: inexistente, en procesamiento, concluido, tipos mezclados, más de 48 h |
0420 · 0421 · 0422 | Consulta de DE: no existe o fue rechazado, RUC sin permiso, encontrado |
1908 | Alícuota de IVA no admitida: solo 10, 5 o exento (la API lo corta con 400 antes de firmar) |
1255 · 1258 · 1259 | Departamento, distrito o ciudad del emisor: el código no coincide con la descripción |
1263 | Leyenda del emisor en ambiente de pruebas |
1321 | Receptor innominado no permitido: el total supera 60.000.000 Gs |
2414 · 2415 · 2417 · 2438 | Documento asociado de una NCE/NDE: tipo, moneda o suma de notas superior al total de la factura |
4009 · 4010 | Fuera de la ventana de cancelación (FE 48 h, resto de tipos 168 h) |
4065 · 4066 · 4067 · 4068 | Inutilización: hay un DE en el rango, ya estaba inutilizado, más de 1000 números o hasta menor que desde |
4150 · 4152 · 4156 · 4200 | Evento del receptor: tope por tipo, ventana de 15 días, conformidad tras desconocimiento |
La lista completa de códigos de SIFEN la publica la DNIT (Manual Técnico del Sistema Nacional de Facturación Electrónica); acá figuran los que la API genera o reenvía con más frecuencia.
SDKs y Postman
Los SDKs de Python, Node.js/TypeScript y PHP traen un recurso sifen con un método por ruta, con el mismo estilo que invoices (list, get, credit_note, debit_note…). Quedan fuera la subida del certificado (multipart) y la descarga del XML: usá HTTP directo. La colección de Postman incluye la carpeta Facturación Paraguay (SIFEN) con todas las rutas.
from yofacturo import YoFacturo
client = YoFacturo(api_key="yf_live_...")
venta = client.sales.create(
items=[{"name": "Yerba mate 1 kg", "quantity": 2, "price": 22000, "metadata": {"vat_rate": 10}}],
request_fiscal_document=True,
idempotency_key="pedido-0001",
)
# ... esperar el webhook invoice.authorized, o leer client.sales.get(id)["data"]["fiscal"]
doc = client.sifen.get("01800695631001001000000112026100710000000013")
client.sifen.void(doc["data"]["cdc"], "Error de carga", idempotency_key="void-0001")La especificación completa (esquemas de cada pedido y respuesta, scopes y errores por ruta) está en /api/v1/openapi.json, bajo la etiqueta Facturación Paraguay (SIFEN).



