API

Documentación para desarrolladores

Scope: invoices · accounting · received_invoices

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

ScopePara quéRutas
invoices:readLeer documentos, XML, eventos, KuDE, catálogos, tipos, numeración, RUC, lotes y link públicoGET /bill/sifen/documents/…, /catalogs/, /types/, /lotes/{protocolo}/, /numbering/, /ruc/{ruc}/, POST /validate-type/, GET …/kude/
invoices:writeOperar documentos: emitir una factura sin venta, consulta en vivo, anular, nota de crédito/débito, inutilización, email, prueba de conectividadPOST /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:writeConfiguració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:writeCrear la venta que dispara la factura y calcular sus totalesPOST /sales/, POST /sales/totals-preview/
received_invoices:writeEventos del receptor sobre DE que recibistePOST /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.
  • POST falla con 409 si ya hay un certificado en ese ambiente: usá PUT para reemplazarlo (el CSC es opcional en PUT, 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_id con 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) o precio_unitario x cantidad, siempre en guaraníes enteros y con el IVA incluido, más tasa_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: 1 y el total como importe). Hasta 50 renglones.
  • Receptor: document_type ruc (se valida con siConsRUC), ci (cédula) o none (innominado, solo bajo 60.000.000 Gs); document_number es obligatorio salvo none.
  • Condición: contado (con pago_tipo, default 1 efectivo; los pagos con tarjeta o cheque necesitan datos que esta ruta no lleva) o credito (plazo_dias, default 30, máx. 365).
  • Caja: sales_point_id (una caja SIFEN con su numeración) o establecimiento + punto_expedicion (un par que el contribuyente tiene configurado, 400 caja_not_configured si no); sin ninguno rige el establecimiento y punto por defecto. Verificá la caja antes con POST /bill/sifen/verify-caja/.
  • Idempotencia: el Idempotency-Key reproduce la respuesta. Además client_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 409 issue_in_progress.
  • Respuesta 201: cdc, number, status: "signed", total, buckets, lines, qr_url y links. Es asíncrona como la factura de una venta: seguila con GET /bill/sifen/documents/{cdc}/ (kind: "standalone", sin sale_id) o con los webhooks. Un token yf_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:

  1. Polling: leé GET /sales/{id}/ y mirá data.fiscal hasta que fiscal.status sea approved (o rejected). Con el CDC también podés usar GET /bill/sifen/documents/{cdc}/.
  2. Webhook: suscribite a invoice.authorized, invoice.rejected e invoice.cancelled; el cuerpo trae system: "sifen", el cdc y 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

EstadoQué significaQué hacer
reserved, signed, sendingNúmero reservado, firmado y en caminoEsperar
sent_sync, sent_lote, in_processingEnviado; SIFEN lo está procesando (los lotes pueden tardar minutos u horas)Esperar el webhook o consultar
approved, approved_obsSIFEN lo aprobó (approved_obs = con observaciones)Listo: el KuDE es válido
rejectedSIFEN lo rechazóLeer sifen_messages, corregir la causa y reemitir con POST /sales/generate_invoice/{sale_id}/ (el documento rechazado libera la venta)
unknownSe perdió la respuesta de SIFEN: nunca se reenvía a ciegasPOST /bill/sifen/documents/{cdc}/consulta/
manualProblema de certificado o TLS que requiere una acciónRevisar readiness y el certificado
staleFirmado hace más de 72 h sin poder transmitirseReemitir
cancelled, inutilizedAnulado 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).

RutaPara 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ónactionQué ocurre
FE aprobada hace menos de 48 hcancelacionEvento de cancelación ante SIFEN; el documento pasa a cancelled
FE aprobada hace 48 h o másnceSe emite una nota de crédito por el saldo que quede
NCE / NDE aprobada hace menos de 168 hcancelacionEvento de cancelación. Fuera de esa ventana: 409, no se puede anular
Número que nunca se aprobó (reservado, rechazado, stale)inutilizacionSe inutiliza ese número
Documento en vuelo (firmado, enviado…)—409: esperá su estado final
Ya anuladocancelledÉ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, reason de 5 a 500 caracteres; serie y timbrado son 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: true sin 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, disconformidad o desconocimiento. reason hasta 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_of con su event_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étodoRutaDescripciónScope
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 cursorinvoices:read
POST/bill/sifen/documents/Emite una FE sin venta (ver "Emitir una factura sin venta"); Idempotency-Key, 30/mininvoices:write
GET/bill/sifen/documents/{cdc}/Estado, número, timbrado, totales, QR, protocolo, sifen_messages y eventosinvoices:read
GET/bill/sifen/documents/{cdc}/xml/XML firmadoinvoices: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ébitoinvoices:write / read
POST/bill/sifen/inutilizaciones/Inutilizar un rangoinvoices: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 pagoinvoices: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 SIFENinvoices:read
GET/bill/sifen/numbering/Último y próximo número de una secuenciainvoices: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

RutaPara 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), inutilizaciones y POST /bill/sifen/documents/ responden 409; diagnose, verify-caja y lotes consultan 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 contra test, y no puede cambiar a prod.
  • Para probar el ciclo completo (emitir, aprobar, anular) usá un token yf_live_* con el contribuyente en environment: 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

HTTPerror_codeCuándo
409use_sifen_routesLlamaste una ruta AFIP /bill/receipts/* en un tenant SIFEN
409 / 404 / 400tax_info_missingNo cargaste los datos fiscales de Paraguay (el código HTTP depende de la ruta)
409certificate_missing · csc_missing · certificate_expired · certificate_not_yet_validEl ambiente no puede firmar (cambio de ambiente, consulta de RUC)
409sandbox_prod_forbiddenUn token yf_test_* pidió pasar a prod
409not_upward · higher_number_in_use · serie_mismatch · counter_movedLa numeración solo avanza, nunca por debajo de un documento emitido
400dv_mismatch · invalid_rucRUC mal formado o DV incorrecto (se rechaza sin llamar a SIFEN)
404ruc_not_foundSIFEN no conoce ese RUC
503sifen_unreachable · email_not_sent · signing_secret_unavailableSIFEN, el correo o el firmador de links no están disponibles: reintentá
400 / 401 / 409invalid_expiry · invalid_token · not_shareable · kude_unavailableLink público o email del KuDE
400type_not_issuable · invalid_tipo_de · invalid_items · invalid_vat_rate · invalid_receptor · invalid_caja · caja_not_configuredPOST /bill/sifen/documents/: el cuerpo no se puede emitir; no se reservó ningún número
409issue_in_progressUna emisión con el mismo client_reference sigue en curso: reintentá en un momento
404 / 400lote_not_found · invalid_protocolGET /bill/sifen/lotes/{protocolo}/
400invalid_contextPOST /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ódigoSignificado
0260DE aprobado (sincrónico)
0300 · 0301Lote recibido / lote no encolado
0360 · 0361 · 0362 · 0363 · 0364Consulta de lote: inexistente, en procesamiento, concluido, tipos mezclados, más de 48 h
0420 · 0421 · 0422Consulta de DE: no existe o fue rechazado, RUC sin permiso, encontrado
1908Alícuota de IVA no admitida: solo 10, 5 o exento (la API lo corta con 400 antes de firmar)
1255 · 1258 · 1259Departamento, distrito o ciudad del emisor: el código no coincide con la descripción
1263Leyenda del emisor en ambiente de pruebas
1321Receptor innominado no permitido: el total supera 60.000.000 Gs
2414 · 2415 · 2417 · 2438Documento asociado de una NCE/NDE: tipo, moneda o suma de notas superior al total de la factura
4009 · 4010Fuera de la ventana de cancelación (FE 48 h, resto de tipos 168 h)
4065 · 4066 · 4067 · 4068Inutilización: hay un DE en el rango, ya estaba inutilizado, más de 1000 números o hasta menor que desde
4150 · 4152 · 4156 · 4200Evento 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).