API REST de FEPY
Todo lo necesario para que tu ERP, POS o sistema propio emita documentos
electrónicos en Paraguay a través de SIFEN. Los ejemplos usan
https://www.fepy.nicosdev.com/api/<tenant> y son ejecutables tal cual: reemplazá
el tenant y el token por los tuyos.
Parte 0Antes de empezar
URL base y tenant
Cada empresa emisora es un tenant: un identificador propio que va en la ruta de todos los endpoints. El equipo de FEPY te entrega el tenant junto con el token cuando da de alta la empresa.
https://www.fepy.nicosdev.com/api/<tenant>
Para verificar que un tenant existe hay un ping sin autenticación. Devuelve
OK con 200 si el tenant está registrado, y Not Found con 404
si no. Es texto plano, no JSON.
Ping del tenant. Limitado a 60 peticiones por minuto.
Autenticación
Todos los endpoints salvo el ping requieren un token fijo por tenant en la cabecera
Authorization. No hay refresh ni expiración corta: el token vive hasta
que se rote.
Authorization: Bearer api_key_<tu-token>
El prefijo api_key_ es opcional: el servidor lo quita si está presente.
Ambas formas funcionan, pero conviene usar siempre la misma para que los logs sean
uniformes.
Con ese token se emiten documentos tributarios a nombre de tu empresa. No lo pongas en código de front-end, en un repositorio, ni en un archivo de configuración versionado. Va en una variable de entorno del servidor.
Códigos HTTP y errores
Solo la capa de autenticación devuelve códigos HTTP de error.
Todo el resto de la API responde siempre HTTP 200, incluso cuando
el documento fue rechazado por SIFEN o el JSON venía mal. El resultado real está
en el campo success del cuerpo.
Si tu cliente hace raise_for_status(), .EnsureSuccessStatusCode()
o equivalente y nada más, vas a tratar como exitoso un rechazo de SIFEN.
Siempre evaluá success.
Códigos que sí devuelve la autenticación
| HTTP | Cuándo |
|---|---|
401 | Falta la cabecera Authorization o no empieza con Bearer |
400 | Falta el parámetro tenant en la ruta |
403 | El token no existe o no corresponde a ese tenant |
503 | Empresa bloqueada, o certificado digital vencido |
Forma del error de negocio
{
"success": false,
"error": "Algunos DEs presentan errores de validación previa",
"errors": [
{ "index": 0, "error": "Faltan datos obligatorios." }
]
}
El campo errors aparece solo cuando el fallo es por documento: cada entrada
trae el index dentro del array que enviaste, para que puedas mapear el error
al documento exacto.
Tu primera llamada
/test confirma tres cosas de una: que el token es válido, en qué ambiente
está la empresa, y si está conectada a SIFEN.
Agregá ?text=t para recibir texto plano en vez de JSON.
curl -H "Authorization: Bearer api_key_<tu-token>" \
https://www.fepy.nicosdev.com/api/<tenant>/test
{
"success": true,
"message": "Conectado con FEPY, hora del Servidor: 2026-09-07 17:25:48",
"data": {
"empresa": "80012345-0 | EMPRESA EMISORA S.A.",
"conectado_sifen": false,
"idCdc": "0001",
"ambiente": "test"
}
}
| Campo | Significado |
|---|---|
empresa | RUC y razón social del emisor detrás del token |
conectado_sifen | true firma y transmite a SIFEN. false genera sin firmar — ver Parte 5 |
idCdc | Identificador del CSC configurado para el QR |
ambiente | test o prod |
La versión ?text=t agrega la última factura aprobada de la empresa, útil
cuando migrás de servidor y necesitás saber en qué numeración quedaste.
Parte 1Adaptá tu sistema
Antes de escribir la primera llamada conviene revisar qué le falta a tu sistema. La facturación electrónica no agrega un endpoint: agrega campos obligatorios en clientes, productos y comprobantes que probablemente hoy no tengas, o tengas como texto libre.
Numeración de documentos
Cada documento se identifica por la terna establecimiento · punto de expedición · número, con ceros a la izquierda. FEPY no lleva el correlativo por vos: el número lo asigna tu sistema y debe ser único e irrepetible por terna.
| Campo | Largo | Ejemplo |
|---|---|---|
establecimiento | 3 dígitos | 001 |
punto | 3 dígitos | 001 |
numero | 7 dígitos | 0000001 |
El resultado visible es 001-001-0000001. Podés mandar los campos sin
padding (por ejemplo 1): FEPY completa los ceros. Lo que no puede hacer
es adivinar cuál sigue.
Dentro de un mismo lote, FEPY detecta la terna duplicada en la validación previa y te devuelve el error antes de firmar. Entre lotes distintos, el rechazo llega de SIFEN. Guardá el correlativo en tu base con un candado de concurrencia.
Clientes y tipoOperacion
El bloque cliente cambia de forma según quién sea el receptor. Las dos
decisiones que gobiernan todo lo demás son contribuyente (si tiene RUC o no)
y tipoOperacion.
tipoOperacion | Caso | Cuándo usarlo |
|---|---|---|
1 | B2B | El cliente tiene RUC — persona física o jurídica |
2 | B2C | Cédula, pasaporte o consumidor final sin documento |
3 | B2G | Organismo del Estado. Exige datos DNCP |
4 | B2F | Receptor del exterior con documento extranjero |
Con contribuyente: true mandás ruc y tipoContribuyente
(1 persona física, 2 persona jurídica). Con contribuyente: false mandás
documentoTipo y documentoNumero.
Si hoy guardás el RUC como texto libre, vas a necesitar normalizarlo y validar el
dígito verificador. El endpoint /ruc/{ruc}
consulta el padrón de SIFEN y devuelve el RUC con su DV calculado — sirve para
depurar la base antes de salir a producción.
Catálogos a mapear
SIFEN trabaja con códigos, no con descripciones. Estos son los catálogos que típicamente hay que agregar a un sistema que nunca facturó electrónicamente:
| Qué | Dónde impacta | Tabla |
|---|---|---|
| Departamento · distrito · ciudad | Establecimiento del emisor y dirección del receptor | departamentos, distritos, ciudades |
| Países | Receptores del exterior | paises |
| Monedas | Cabecera del documento y entregas | monedas |
| Unidades de medida | Cada ítem | unidades-medida |
| Tipos de documento de identidad | Receptores sin RUC | tipos-documentos-identidades |
| Condiciones de pago | Contado / crédito y formas de pago | condiciones-tipos-pagos |
Los tres primeros están encadenados: la ciudad pertenece a un distrito y el distrito a un departamento. Si mandás una combinación inconsistente, el generador la rechaza. El apéndice tiene el catálogo completo, y un ZIP con todo.
Qué persistir de la respuesta
La respuesta de emisión trae datos que tu sistema necesita guardar. Si no los guardás, después no podés consultar el estado ni reimprimir el KUDE.
| Campo | Por qué guardarlo |
|---|---|
cdc | Clave del documento, 44 dígitos. Es la llave para consultar, cancelar, descargar XML y KUDE |
loteId | Permite consultar el estado del envío completo |
situacion | Estado numérico del documento (ver tabla de situaciones) |
respuesta_codigo y respuesta_mensaje | Motivo del rechazo cuando lo hay — sin esto no sabés qué corregir |
qr | URL del QR impreso en el KUDE |
numeroFactura | Terna formateada, para mostrar al usuario |
El campo xml trae el documento firmado completo. Podés guardarlo o
pedirlo después con /de/xml/{cdc}. La
conservación del XML por el plazo legal es responsabilidad del emisor.
Parte 2Emisión de documentos
Hay dos caminos para emitir: por lote (asíncrono, el recomendado) y
documento a documento (síncrono). Salvo que tengas una razón concreta
para el segundo, usá el lote — es el que soporta borradores, el que devuelve
loteId para seguimiento y el que se usa en producción.
POST /lote/create
Recibe un array JSON de documentos, todos del mismo
tipoDocumento. Query opcional: ?draft=true crea el lote
en borrador sin transmitirlo a SIFEN.
Tipos de documento
tipoDocumento | Documento |
|---|---|
1 | Factura Electrónica |
4 | Autofactura Electrónica |
5 | Nota de Crédito Electrónica |
6 | Nota de Débito Electrónica |
7 | Nota de Remisión Electrónica |
Si mezclás tipos en el mismo array, la validación previa devuelve
"Tipo de documento inconsistente." con el índice del documento
culpable. Armá un lote por tipo.
Si un documento usa un número cubierto por una inutilización aprobada (mismo tipo,
establecimiento y punto, en el ambiente actual de la empresa), la validación previa
lo rechaza sin guardar nada: "Existe número inutilizado en el rango solicitado:
numero=15, establecimiento=001, punto=001 [4066]" con el índice del documento.
Emití con un número nuevo.
Ejemplo mínimo: una factura
curl -X POST https://www.fepy.nicosdev.com/api/<tenant>/lote/create \
-H "Authorization: Bearer api_key_<tu-token>" \
-H "Content-Type: application/json" \
-d @factura.json
[
{
"tipoDocumento": 1,
"establecimiento": "001",
"punto": "001",
"numero": "0000001",
"fecha": "2026-09-07T10:00:00",
"tipoTransaccion": 2,
"tipoImpuesto": 1,
"moneda": "PYG",
"cliente": {
"contribuyente": true,
"ruc": "4303489-6",
"razonSocial": "CLIENTE DE PRUEBA S.A.",
"tipoOperacion": 1,
"pais": "PRY",
"tipoContribuyente": 2,
"codigo": "001"
},
"factura": { "presencia": 1 },
"items": [
{
"codigo": "SRV001",
"descripcion": "Servicio de prueba",
"unidadMedida": 77,
"cantidad": 1,
"precioUnitario": 550000,
"ivaTipo": 1,
"ivaBase": 100,
"iva": 10
}
],
"condicion": {
"tipo": 1,
"entregas": [
{ "tipo": 1, "monto": 550000, "moneda": "PYG" }
]
}
}
]
Estructura del documento
Cabecera
| Campo | Descripción | |
|---|---|---|
tipoDocumento | requerido | Tipo del documento (tabla de arriba) |
establecimiento | requerido | 3 dígitos |
punto | requerido | 3 dígitos |
numero | requerido | 7 dígitos |
fecha | requerido | ISO 8601 sin zona: 2026-09-07T10:00:00 |
tipoTransaccion | opcional | Naturaleza de la operación |
tipoImpuesto | opcional | 1 IVA |
moneda | requerido | ISO 4217: PYG, USD… |
cambio | opcional | Tipo de cambio si la moneda no es PYG |
codigoSeguridadAleatorio | opcional | 9 dígitos. Si no lo mandás, FEPY lo genera |
SIFEN valida la coherencia entre la fecha de emisión y la de firma
(validación A004). Mandá la fecha en America/Asuncion, nunca en UTC:
un desfasaje de horas puede caer fuera de la ventana aceptada.
Bloque cliente
| Campo | Descripción | |
|---|---|---|
contribuyente | requerido | true si tiene RUC |
tipoOperacion | requerido | B2B / B2C / B2G / B2F — ver Parte 1 |
razonSocial | requerido | Nombre del receptor |
pais | requerido | ISO 3166: PRY |
ruc | condicional | Con contribuyente: true. Formato NNNNNNN-D |
tipoContribuyente | condicional | Con RUC: 1 física, 2 jurídica |
documentoTipo | condicional | Con contribuyente: false |
documentoNumero | condicional | Con contribuyente: false |
correo | opcional | Destino del envío automático del KUDE |
direccion, numeroCasa, departamento, distrito, ciudad | opcional | Domicilio del receptor |
Bloque items
| Campo | Descripción | |
|---|---|---|
codigo | requerido | Código interno del producto o servicio |
descripcion | requerido | Detalle que se imprime en el KUDE |
unidadMedida | requerido | Código SIFEN — 77 es unidad |
cantidad | requerido | Admite decimales |
precioUnitario | requerido | Precio con IVA incluido |
ivaTipo | requerido | 1 gravado, 2 exonerado, 3 exento, 4 gravado parcial |
ivaBase | requerido | Proporción gravada: 100 salvo gravado parcial |
iva | requerido | Tasa: 10, 5 o 0 |
descuento | opcional | Descuento por ítem |
Bloque condicion
tipo: 1 es contado y lleva entregas. tipo: 2 es
crédito y lleva el bloque credito con plazo o cuotas.
"condicion": {
"tipo": 2,
"credito": {
"tipo": 1,
"plazo": "30 días"
}
}
Bloques por tipo de documento
| Bloque | Aplica a | Contenido |
|---|---|---|
factura | Factura (1) | presencia: indicador de presencia de la operación |
autoFactura | Autofactura (4) | Datos del vendedor no contribuyente |
notaCreditoDebito | NC (5) y ND (6) | motivo de la nota |
remision | Nota de remisión (7) | Motivo, responsable y datos del traslado |
documentoAsociado | NC, ND y remisión | cdc del documento original y formato |
Respuesta y situaciones
{
"success": true,
"result": {
"loteId": 42,
"deList": [
{
"numeroFactura": "001-001-0000001",
"cdc": "01800123450001001000000112026090711234567890",
"serie": "",
"respuesta_codigo": "0260",
"respuesta_mensaje": "Aprobado",
"situacion": 1,
"estado": "Aprobado",
"fechaFirma": "2026-09-07T11:47:58",
"fechaEmision": "2026-09-07T10:00:00",
"loteId": 42,
"isEncontrado": false,
"qr": "https://ekuatia.set.gov.py/consultas/qr?nVersion=150&Id=...",
"xml": "<?xml version=\"1.0\"?>..."
}
]
}
}
Situaciones
situacion | Estado | Qué significa |
|---|---|---|
-1 | Borrador | Generado y guardado, pendiente de confirmación |
0 | Generado | XML armado, todavía no transmitido |
1 | Encolado | Enviado a SIFEN, esperando el resultado del lote |
2 | Aprobado | Documento válido |
3 | Aprobado con observación | Válido, con advertencias de SIFEN |
4 | Rechazado | SIFEN lo rechazó. El motivo viene en respuesta_mensaje |
99 | Cancelado | Se le aplicó un evento de cancelación |
90 | Inutilizado | Su número quedó dentro de un evento de inutilización aprobado. Lo informan las consultas; no se guarda en el documento. No reintentar ese número |
El lote es asíncrono: SIFEN acusa recibo y procesa después. Un
documento en situacion: 1 todavía puede terminar rechazado. Para
cerrar el circuito consultá
/lote/{id} o
/cdc/{cdc} unos segundos más tarde.
Borradores y confirmación
Con ?draft=true el lote se genera, se firma y se guarda, pero
no se transmite. Sirve para previsualizar el KUDE antes de emitir en
firme — el caso típico es el mostrador, donde el cliente todavía puede cambiar algo.
Devuelve loteId y los documentos en situacion: -1.
Transmite a SIFEN un lote que está en borrador. Sin cuerpo.
| Error | Causa |
|---|---|
Lote no encontrado | El loteId no existe o es de otra empresa |
Lote ya procesado | Ese lote no está en borrador — confirmar dos veces no duplica la emisión |
POST /lote/qr
Mismo cuerpo que /lote/create, pero no persiste nada:
valida, arma el XML y devuelve solo cdc y qr.
Es el endpoint ideal para probar un payload nuevo: si pasa acá, pasa en
/lote/create, y no te deja documentos basura en la base.
{
"success": true,
"result": {
"deList": [
{
"cdc": "01800123450001001000000112026090711234567890",
"qr": "https://ekuatia.set.gov.py/consultas/qr?nVersion=150&Id=..."
}
]
}
}
POST /de/create (síncrono)
Emite un documento y espera la respuesta de SIFEN en la misma llamada. Acepta un objeto o un array — si mandás un array, procesa el primer elemento.
La respuesta es el envelope de SIFEN parseado, no el deList del lote. El
estado resuelto queda persistido con situacion 2, 3 o 4 según lo que
devuelva SIFEN.
Bloquea tu proceso mientras SIFEN responde, no admite borradores, y emite de a un
documento. Para volumen o para cualquier flujo donde el usuario esté esperando en
pantalla, usá /lote/create.
Parte 3Consultas y descargas
Como la emisión por lote es asíncrona, estas consultas son las que cierran el circuito: confirman si SIFEN aprobó, rechazó o todavía está procesando.
Estado de un lote
Devuelve el estado de todos los documentos del lote. Query:
?qr=f y ?xml=f omiten esos campos — útil si solo querés
los estados y no querés arrastrar los XML.
curl -H "Authorization: Bearer api_key_<tu-token>" \
"https://www.fepy.nicosdev.com/api/<tenant>/lote/42?xml=f"
El lote debe pertenecer a la empresa del token. Si no, la respuesta es
Lote no encontrado — no se filtra información de otros tenants.
Estado por CDC
Estado de un documento propio. Con ?forzar=t además
consulta SIFEN en vivo y agrega el bloque consulta_forzada.
{
"success": true,
"data": {
"numeroFactura": "001-001-0000001",
"cdc": "0180012345000100100000001...",
"is_conectado": true,
"ambiente": "t",
"respuesta_codigo": "0260",
"respuesta_mensaje": "Aprobado",
"situacion": 2,
"estado": "Aprobado",
"fechaFirma": "2026-09-07T11:47:58",
"fechaEmision": "2026-09-07T10:00:00",
"qr": "https://ekuatia.set.gov.py/consultas/qr?...",
"json": { },
"xml": "<?xml ...",
"eventos": [],
"loteId": 42
}
}
Cuando situacion es 4, la respuesta agrega un bloque
error con el código, el mensaje y el XML de respuesta del lote. Ahí
está el motivo exacto del rechazo.
Estados en bloque
Estado de varios CDC en una sola llamada. Query: ?force=t reconsulta
cada uno contra SIFEN; ?qr=f y ?xml=f alivianan la respuesta.
{
"cdcList": [
{ "cdc": "0180012345000100100000001..." },
{ "cdc": "0180012345000100100000002..." }
]
}
La respuesta es {"success": true, "deList": [...]} con un elemento por CDC
encontrado. Los CDC inexistentes se omiten en silencio: compará contra la lista que
enviaste para detectar faltantes.
force=t es caro
Con force los CDC se consultan a SIFEN uno por uno, en
serie. Una lista larga puede tardar. Usalo para reconciliar, no en el
camino caliente de tu aplicación.
CDC de terceros
Consulta directa a SIFEN de un documento que no emitiste vos — por ejemplo, para validar una factura de proveedor.
| Query | Resultado |
|---|---|
| sin parámetros | XML crudo del documento (text/xml) |
?json=t | Campos extraídos del XML, en JSON |
?detallado=t&json=t | JSON con el envelope completo |
Si el documento no existe o fue cancelado, la respuesta es
{"success": false, "error": "CDC no encontrado o cancelado"}.
Consulta de RUC
Consulta el padrón de SIFEN. Podés mandar el RUC con o sin dígito verificador: si lo mandás con guion, se ignora la parte del DV para consultar.
En la respuesta, el campo dRUCCons vuelve con el DV calculado
por FEPY (módulo 11). Sirve para normalizar la base de clientes sin implementar el
cálculo por tu cuenta.
curl -H "Authorization: Bearer api_key_<tu-token>" \
https://www.fepy.nicosdev.com/api/<tenant>/ruc/4303489
Descarga del XML
Devuelve el XML firmado como descarga (application/xml, nombre de
archivo <cdc>.xml).
Solo entrega documentos en situación aprobado (2), aprobado con
observación (3) o cancelado (99), y solo de tu propia empresa.
Cualquier otro caso responde 404 sin cuerpo.
Descarga del KUDE
Genera el PDF de representación gráfica de uno o varios documentos.
{
"cdcList": [
{ "cdc": "0180012345000100100000001..." }
]
}
Con un solo CDC el archivo se llama <Tipo>_<numeroFactura>.pdf.
Con varios, los documentos van en un único PDF llamado
<Tipo>_cant_<N>.pdf. El formato de hoja sale de la configuración
de la empresa o del establecimiento.
Parámetros de la empresa
Devuelve la configuración del emisor: RUC, razón social, timbrado, establecimientos, actividades económicas y ambiente.
Útil para que tu sistema lea de acá los establecimientos y puntos habilitados en vez de tenerlos duplicados en tu configuración.
El bloque certificado viene recortado a uuid y
expiracion. El archivo .pfx y su passphrase nunca salen
por la API — un token de tenant no puede extraer la clave de firma.
Parte 4Eventos y anulación
Un documento electrónico aprobado no se borra ni se edita. Lo que se hace es registrar un evento sobre él, o emitir una nota de crédito que lo compense. Todos los eventos van por el mismo endpoint, cambiando el tipo en la ruta.
{tipo} es cancelacion, inutilizacion o
nominacion. Cualquier otro valor devuelve
Evento inexistente o no soportado.
Respuesta
La respuesta trae result con el envelope de SIFEN y, sobre todo,
isExitoso: el booleano que indica si SIFEN aprobó el evento. Cuando el envío
falla, el evento no queda registrado.
{
"success": true,
"isExitoso": true,
"result": {
"ns2:rRetEnviEventoDe": {
"ns2:gResProcEVe": {
"ns2:dEstRes": "Aprobado",
"ns2:gResProc": {
"ns2:dCodRes": "0600",
"ns2:dMsgRes": "Evento registrado exitosamente"
}
}
}
}
}
success e isExitoso no son lo mismo
success: true significa que la comunicación con SIFEN salió bien.
isExitoso es lo que dice SIFEN del evento en sí. Podés recibir
success: true con isExitoso: false — el evento fue rechazado.
Validá los dos.
Cancelación
Anula un documento aprobado. Al aprobarse, FEPY marca el documento local en
situacion: 99 (Cancelado).
{
"cdc": "01800123450001001000000112026090711234567890",
"motivo": "Error en los datos del receptor"
}
SIFEN acepta la cancelación dentro de una ventana desde la emisión — 48 horas para facturas, más amplia para otros documentos. Pasado el plazo, el evento se rechaza y el camino que queda es la nota de crédito.
Inutilización
Declara ante SIFEN un rango de numeración que nunca se va a usar. Es para saltos de correlativo: se cortó la luz, se perdió una numeración, se anuló un preimpreso.
{
"tipoDocumento": 1,
"establecimiento": "001",
"punto": "001",
"desde": 15,
"hasta": 18,
"motivo": "Salto de numeracion por corte de energia",
"timbrado": "12345678"
}
No lleva cdc: no anula documentos, reserva números. Si el rango incluye
un número ya emitido, SIFEN rechaza el evento.
Una vez aprobado, las consultas de estado (cdc, de/estado,
lote/{id}) informan con situacion: 90 (Inutilizado),
inutilizado: true y fechaInutilizacion todo documento local de
ese tipo, establecimiento y punto cuyo número caiga en el rango y que no esté en cola
(0/1), aprobado (2/3) ni cancelado
(99). El 90 se calcula al responder: no se guarda en el documento y esos
números no se consultan en SIFEN.
Si algún documento del rango todavía está en cola (0/1), el
evento se rechaza sin registrarse ni enviarse a SIFEN:
"Hay documentos en cola dentro del rango a inutilizar. Espere el resultado del lote
antes de enviar el evento." Lo mismo vale para cancelación y nominación de un
documento en cola: "El documento todavía está en cola en SIFEN. Espere el
resultado del lote antes de enviar el evento."
Un lote nuevo que use un número del rango se rechaza en la validación previa con
[4066] Existe número inutilizado en el rango solicitado, sin guardar nada.
Nominación
Asigna o corrige los datos del receptor de un documento ya aprobado — el caso clásico es la factura emitida a consumidor final que el cliente después pide a su RUC.
{
"cdc": "01800123450001001000000112026090711234567890",
"cliente": {
"contribuyente": true,
"ruc": "4303489-6",
"razonSocial": "CLIENTE DE PRUEBA S.A.",
"tipoOperacion": 1,
"pais": "PRY",
"tipoContribuyente": 2
}
}
Anulación por nota de crédito
Atajo que emite una nota de crédito total contra una factura aprobada, sin que tengas que armar el documento vos.
{
"cdcFactura": "01800123450001001000000112026090711234567890",
"comprobante": "001-001-0000045"
}
| Campo | Reglas |
|---|---|
cdcFactura | 44 dígitos y debe empezar con 01 — solo se anulan facturas |
comprobante | Numeración de la nota de crédito, formato XXX-XXX-XXXXXXX, exactamente 15 caracteres |
FEPY toma los ítems, el cliente y la moneda de la factura original, arma la nota de
crédito con tipoDocumento: 5, motivo 2 y el documento
asociado apuntando al CDC, y la emite. La respuesta es la de
/lote/create más un bloque info_anulacion.
comprobante es la numeración que va a llevar la nota de crédito, no la
de la factura anulada. Sale de tu correlativo de notas de crédito.
Dentro del plazo, cancelar es más limpio: el documento queda anulado y no genera
un comprobante nuevo. La nota de crédito es el camino cuando el plazo venció, o
cuando la devolución es parcial — en ese caso armá la NC a mano con
/lote/create, porque este atajo siempre es total.
Parte 5Desarrollar sin certificado
Conseguir el certificado digital lleva tiempo, y no tiene sentido que tu integración espere. Una empresa puede quedar configurada en modo desconectado: valida todo, arma el XML, calcula el CDC y devuelve documentos aprobados — sin firmar y sin transmitir a SIFEN.
Lo reconocés en /test: el campo
conectado_sifen viene en false.
Qué hace el modo desconectado
| Paso | Modo desconectado |
|---|---|
| Validación previa del lote (terna, tipo consistente, duplicados) | se ejecuta |
| Validación estructural del JSON del documento | se ejecuta |
| Generación del XML y cálculo del CDC | se ejecuta |
| Persistencia del documento en la base | se ejecuta |
| Firma XMLDSig con el certificado | se omite |
| Generación del QR | se omite |
| Transmisión a SIFEN | se omite |
Es decir: todo el trabajo de integración se puede hacer y probar. El armado del payload, el mapeo de catálogos, el manejo de errores de validación y la numeración se ejercitan igual que en producción.
{
"success": true,
"message": "Lote creado con éxito - Sin conexión a SIFEN",
"result": {
"loteId": 0,
"deList": [
{
"numeroFactura": "001-001-0000001",
"cdc": "01800123450001001000000112026090711234567890",
"xml": "<?xml version=\"1.0\"?>..."
}
]
}
}
Los documentos quedan guardados con respuesta_codigo: "0260",
estado: "Aprobado" y situacion: 1. Son aprobaciones
simuladas: te sirven para desarrollar el flujo completo de tu lado.
Límites y trampas
-
loteIdviene en0. El lote sí se creó en la base, pero la respuesta devuelve cero. Si tu sistema guarda ese valor y después consulta/lote/0, no va a encontrar nada. -
No hay QR ni XML firmado. El campo
qrviene vacío yxmltrae el documento sin firma. El KUDE que generes en este modo no tiene un QR válido — sirve para maquetar, no para entregar. -
El ciclo asíncrono no se simula. No hay protocolo de lote, no
hay consulta posterior, no hay transición de estados. El documento nace
aprobado. La lógica de reintento y reconciliación contra
/lote/{id}no se ejercita acá.
Qué probar igual antes de conectar
| Probable en desconectado | Requiere certificado |
|---|---|
| Armado del payload y mapeo de catálogos | Firma y validación criptográfica de SIFEN |
| Errores de validación previa y estructural | Rechazos de reglas de negocio de SIFEN |
| Control de numeración y duplicados | Ciclo Encolado → Aprobado / Rechazado |
| Persistencia del CDC en tu base | QR, KUDE definitivo y eventos |
En una empresa de desarrollo el timbrado y su fecha suelen ser de relleno: alcanzan
para generar el XML, pero SIFEN los rechaza. Antes de activar
conectado_sifen, cargá el timbrado real junto con el certificado.
Cuando el certificado esté cargado y la empresa se conecte, tu integración no cambia:
los mismos endpoints empiezan a devolver qr, XML firmado y situaciones
reales de SIFEN.
ApéndiceTablas de códigos SIFEN
Estas tablas salen del mismo catálogo que usa el generador de XML, no de una copia pegada en la documentación. Lo que descargás acá es lo que valida tus documentos.
Descargar las tablas
Cada tabla está en CSV —para abrir en Excel o importar a tu base— y en JSON, para sembrarla desde un script. El CSV lleva BOM, así que Excel en Windows respeta los acentos sin que tengas que tocar nada.
Catálogo completo
Las 40 tablas en CSV y JSON, más un LEEME.txt con el índice y el conteo de filas.
Ninguna tabla coincide con ese filtro.
Cada fila de distritos declara su departamento, y cada
fila de ciudades su distrito. Importá las tres tablas y
respetá la cadena: una combinación inconsistente hace fallar la generación del XML.
Consumo por HTTP
Las mismas tablas se sirven sin autenticación, para que puedas actualizarlas desde un script en vez de bajarlas a mano.
Lista las tablas disponibles con su slug, etiqueta y cantidad de filas.
Devuelve la tabla como JSON en línea, envuelta en {success, tabla, total, data}.
Entrega la tabla como archivo adjunto. El JSON acá es el array pelado, sin envoltorio.
El catálogo completo en un ZIP, con las carpetas csv/ y json/.
curl https://www.fepy.nicosdev.com/manual/codigos
curl -O https://www.fepy.nicosdev.com/manual/codigos/unidades-medida.csv
curl -O https://www.fepy.nicosdev.com/manual/codigos/todo.zip
{
"success": true,
"tablas": [
{ "tabla": "departamentos", "etiqueta": "Departamentos", "filas": 18 },
{ "tabla": "distritos", "etiqueta": "Distritos", "filas": 252 }
]
}
Un slug fuera del catálogo devuelve 404 con
{"success": false, "error": "Tabla inexistente"}.