Manual de integración
Cómo enviar tu JSON a la API
Tu sistema (ERP, punto de venta, aplicación en Delphi o cualquier otro) arma un JSON con los datos de la venta y lo envía por HTTP POST. La API hace todo lo demás con SIFEN. Este documento es el contrato completo: con esto alcanza para integrar.
00 Qué hace esta API
El sistema emisor solo arma el JSON de la venta y lo manda. La API se encarga de todo lo de SIFEN:
- genera el CDC (los 44 dígitos de control)
- genera el XML rDE y lo firma digitalmente con el certificado de la empresa
- calcula el código QR (
dCarQR/cHashQR) - arma el lote, lo envía a SIFEN y hace el seguimiento del estado
- guarda el XML firmado y el JSON original
- genera el KUDE (la representación gráfica en PDF para el cliente)
El sistema emisor no necesita certificado, CSC, librerías de XML o firma, ni conocer los WebServices de SIFEN.
Tipos de comprobante que emite la API — campo tipoDocumento
1 | Factura Electrónica — el caso normal |
4 | Autofactura Electrónica — compra a un no contribuyente / extranjero; el emisor se factura a sí mismo |
5 | Nota de Crédito Electrónica |
6 | Nota de Débito Electrónica |
7 | Nota de Remisión Electrónica — traslado de mercadería; sin valores ni IVA |
Por defecto solo están habilitados 1, 5 y 6. Para emitir Autofactura (4) o Nota de Remisión (7) el proveedor debe habilitarlas para tu empresa; si mandás un tipo no habilitado la API responde 422 (ver N13).
Inicio rápido
- Pedí al proveedor de la API tu
{BASE_URL}y un token (o el alta de tu IP para la ruta legacy). - Armá el JSON de la venta según el contrato y hacé
POST {BASE_URL}/api/v1/documentoscon la cabeceraAuthorization: Bearer <TOKEN>. - Guardá el
cdcy elnro_lotede la respuesta. El comprobante ya es válido; el KUDE se puede imprimir aunque el estado seaPendiente.
01 La URL de la API puede variar
Esta API funciona bajo cualquier URL: dominio propio, subdominio, IP con puerto o incluso una subcarpeta. Ejemplos válidos de base:
https://facturacion.miempresa.com https://erp.miempresa.com.py:8443 http://192.168.10.20:8080 https://apps.miempresa.com/facturacion
En este manual esa parte se escribe como {BASE_URL} (sin barra final). El proveedor de la API te entrega el valor real al dar de alta la empresa. Todos los endpoints se arman igual: {BASE_URL} + ruta.
API nueva — recomendada para sistemas nuevos
| Método | Ruta | Para |
|---|---|---|
| POST | /api/v1/documentos | emitir |
| GET | /api/v1/documentos/{cdc} | ver estado |
| GET | /api/v1/documentos/{cdc}/xml | XML firmado |
| GET | /api/v1/documentos/{cdc}/json | JSON original |
| GET | /api/v1/documentos/{cdc}/qr | URL del QR |
| GET | /api/v1/documentos/{cdc}/kude?format=pdf | KUDE PDF |
| GET | /api/v1/documentos/{cdc}/kude?format=html | KUDE HTML |
| POST | /api/v1/documentos/{cdc}/reenviar | reintentar el envío (ver) |
| POST | /api/v1/documentos/{cdc}/anular | anular |
| POST | /api/v1/documentos/{cdc}/consultar | forzar consulta a SIFEN |
| GET | /api/v1/lotes/{nroLote} | estado del lote |
| GET | /api/v1/salud | diagnóstico (sin token) |
API legacy — compatible con el sistema PHP anterior
El cliente Delphi existente sigue funcionando cambiando solo el host de la URL. La ruta y el formato de respuesta no cambian.
| Método | Ruta | Para |
|---|---|---|
| POST | /api-py/firmar-enviar.php | emitir |
| POST | /api-py/anular.php | anular |
| GET | /api-py/consultar.php?lote={n} | estado del lote |
| GET | /api-py/get_xml.php?cdc={cdc} | XML firmado |
| GET | /api-py/get_json.php?cdc={cdc} | JSON original |
| GET | /api-py/get_qr.php?cdc={cdc} | URL del QR |
| GET | /api-py/kude.php?cdc={cdc}&format=pdf | KUDE |
| GET | /api-py/diagnostico.php | diagnóstico |
Sobre los enlaces que devuelve la API
La respuesta de emitir incluye un bloque links con URLs absolutas ya armadas (xml, json, kude, qr). Usalas tal cual: la API las construye a partir de la misma URL con la que llegó tu request, así que siempre apuntan al lugar correcto.
El qr_url del comprobante apunta a SIFEN (ekuatia.set.gov.py), no a esta API. Nunca lo reconstruyas por tu cuenta: usalo tal como viene.
02 Autenticación
API nueva — token Bearer
Cabecera Authorization: Bearer <TOKEN>. El token se genera una vez en el panel de administración: {BASE_URL}/admin/tokens (o {BASE_URL}/e/{tu-slug}/admin/tokens si tu proveedor activó el modo multiempresa, ver §15) → «Nuevo token» → se copia (no se vuelve a mostrar). Un token por sistema consumidor.
API legacy (/api-py/*)
Sin token de sesión. El acceso se restringe opcionalmente por lista de IPs y/o una cabecera X-Legacy-Token, ambas configuradas del lado del servidor. Si tu IP está permitida, no mandás nada.
El ambiente (test / producción) lo decide la API por su propia configuración. El sistema emisor no lo envía.
03 Reglas que debe cumplir el sistema emisor
Leer antes de integrar. Cada regla tiene un identificador (N0…N14) que se referencia en el resto del manual.
N0 recordID
Cada envío lleva recordID (string, ≤ 20) con el identificador de la empresa emisora (ej. 5F-7111E5F9). Lo entrega el proveedor de la API. No cambia entre comprobantes y no aparece en el XML ni en el KUDE: solo sirve para validar y enrutar la empresa. Si no coincide → HTTP 403.
N1 Numeración
El sistema emisor es el dueño del número. Enviar siempre establecimiento y punto como strings de 3 dígitos ("001") y numero como string de 7 dígitos con ceros a la izquierda ("0000123"). No repetir un número ya emitido: si se repite → HTTP 409 (y en la ruta legacy, un error en el cuerpo). Ver Idempotencia.
N2 Fecha
fecha siempre en formato YYYY-MM-DDTHH:MM:SS (hora de Paraguay). La fecha es parte del CDC: un error acá invalida el documento.
N3 CDC
Normalmente no se envía (la API lo genera). Enviarlo solo si el sistema externo ya lo calculó (facturación offline o contingencia). Si se envía, manda sobre establecimiento, punto, numero, tipoEmision y codigoSeguridadAleatorio (esos campos se toman del CDC).
N4 Importes
Números JSON, sin separador de miles, con punto decimal.
45000 · 45000.00"45.000" · "45,000" · "45000,00"N5 IVA por ítem — lo recalcula la API
Mandá por ítem solo totalItem, ivaTipo, iva (tasa 0/5/10) y ivaBase (proporción gravada, 100 = todo). La API calcula basGravIVA y liqIVAItem con la fórmula exacta de SIFEN e ignora lo que mandes en esos dos campos (si no coinciden con SIFEN, el DE se rechaza — código 1911).
dBasGravIVA = 100 * totalItem * ivaBase / (10000 + ivaBase * tasa)
dLiqIVAItem = dBasGravIVA * tasa / 100
ejemplo: totalItem 45000 al 10% → basGravIVA 40909.09090909
liqIVAItem 4090.90909091
Exento (ivaTipo 3) / Exonerado (ivaTipo 2): basGravIVA = 0 ; liqIVAItem = 0Si no mandás unidadMedida, se asume 77 (UNI). cUniMed / dDesUniMed son obligatorios en el XML.
N6 Totales
La API recalcula a partir de los ítems: dSubExe, dSubExo, dSub5, dSub10, dTotOpe, dTotGralOpe, dIVA5, dIVA10, dTotIVA, dBaseGrav5, dBaseGrav10, dTBasGraIVA.
El sistema emisor igual debe mandar el bloque totales con estos 8 campos (van tal cual al XML). Si no hay descuentos ni anticipos, todos en 0: dTotDesc, dTotDescGlotem, dTotAntItem, dTotAnt, dPorcDescTotal, dDescTotal, dAnticipo, dRedon.
N7 Pagos
Si condicion.tipo = 1 (contado): la suma de condicion.contado[].monto debe coincidir con el total del comprobante.
N8 Cliente sin RUC
contribuyente = false + TipoDocumentoIdentidad + NumeroDocumentoIdentidad. Ojo: esas dos claves llevan mayúsculas iniciales. Consumidor final sin datos: TipoDocumentoIdentidad = 5 (Innominado); la API pone dNumIDRec = 00000000 y dNomRec = "Sin Nombre".
N9 Nota de crédito / débito
tipoDocumento = 5 (crédito) o 6 (débito), con notaCreditoDebito.motivo (1–8, default 1) y documentoAsociado (formato 1 = electrónico + cdc de la factura original de 44 dígitos). En NC/ND no se envía el bloque factura.
N10 Reintentos ante timeout de red
Si la API no responde: no generes un número nuevo. Reintentá el mismo JSON con el mismo número. Si además mandás la cabecera Idempotency-Key, el reintento devuelve el documento ya emitido (HTTP 200) en vez de un 409.
N11 Autofactura Electrónica (tipoDocumento = 4)
Se usa cuando el emisor compra a alguien que no puede emitir factura (no contribuyente, microproductor, extranjero). El emisor se factura a sí mismo.
cliente(receptor) debe ser el propio emisor:contribuyente = trueyruc= el RUC del emisor (número-DV). Si no coincide →422.- No se envía el bloque
factura. - Sí se envía
condicion(contado/crédito) eitemscon IVA, como en una factura normal. - Bloque
vendedorobligatorio (datos de a quién se le compró). documentoAsociadoconformato 3(constancia electrónica de no ser contribuyente / de microproductor) es lo habitual, pero no se exige.
N12 Nota de Remisión Electrónica (tipoDocumento = 7)
Ampara el traslado de mercadería. No traslada valores: no lleva precios, ni IVA, ni totales.
- No se envía:
factura,condicion,totales,tipoImpuesto,condicionAnticipo. items[]solo describen la mercadería:codigo,descripcion,cantidad,unidadMedida,pais/paisDescripcion(ncm/observacionopcionales). No mandarprecioUnitario/ivaTipo/iva/totalItem.- Bloques
remisionytransporteobligatorios. cliente(a quién se entrega) sí se envía, igual que siempre.monedase sigue enviando (formalidad; no afecta el traslado).
N13 Tipos de documento habilitados por empresa
Cada empresa tiene una lista de tipos permitidos. El default es 1, 5 y 6. Autofactura (4) y Nota de Remisión (7) ya están implementadas, pero el proveedor debe agregarlas a tu empresa (una vez probadas en test). Si enviás un tipoDocumento fuera de tu lista → HTTP 422 con el error en el campo tipoDocumento («El tipo de documento N no esta habilitado para esta empresa.»).
N14 Venta al Estado / B2G (cliente.tipoOperacion = 3)
Se usa cuando el receptor es una entidad de gobierno (ej. un ministerio).
- Bloque
compraPublicaobligatorio:modalidad,entidad,anio,secuenciayfechaCodigo(el código de contratación que emite la DNCP para ese proceso de compra). Sin este bloque →HTTP 422. fechaCodigono puede ser posterior afecha(fecha de emisión del comprobante) →HTTP 422si lo es.- Para cualquier otro
tipoOperacion(1, 2 o 4) no se envíacompraPublica; si se envía igual, la API lo ignora. - Por ahora solo implementado para Factura Electrónica (
tipoDocumento 1); ver ejemplo 5.8.
04 Contrato JSON — campo por campo
Leyenda: R requerido O opcional C condicional.
Raíz
| Campo | Tipo / valores | |
|---|---|---|
| R | recordID | string ≤ 20 — identificador de la empresa (N0) |
| O | cdc | string 44 dígitos (N3) |
| C | tipoDocumento | int 1 | 4 | 5 | 6 | 7 (default 1) — ver N11–N13 |
| R | tipoEmision | int 1 Normal, 2 Contingencia |
| R | establecimiento | string(3) |
| R | punto | string(3) |
| R | numero | string(7) |
| O | codigoSeguridadAleatorio | string(9) — si falta, la API genera uno |
| R | fecha | YYYY-MM-DDTHH:MM:SS |
| R | moneda | PYG (también USD, BRL, ARS, EUR) |
| C | tipoImpuesto | int 1–5 (1 = IVA) — no enviar si tipoDocumento = 7 |
| O | tipoTransaccion | int 1–13 |
| C | condicionAnticipo | int 1 | 2 — no enviar si tipoDocumento = 7 |
| O | observacion | string → dInfoEmi |
| O | descripcion | string → dInfoFisc |
| O | timbradoNumero / timbradoFecha | normalmente lo pone la API |
factura — obligatorio solo si tipoDocumento = 1
| Campo | Tipo / valores | |
|---|---|---|
| R | presencia | int 1–6 |
| O | fechaEnvio | fecha |
vendedor — obligatorio solo si tipoDocumento = 4 (Autofactura, N11)
| Campo | Tipo / valores | |
|---|---|---|
| R | naturaleza | int 1 No contribuyente, 2 Extranjero |
| R | tipoDocumentoIdentidad | int 1–4 |
| R | numeroDocumentoIdentidad | string ≤ 20 |
| R | nombre | string ≤ 60 |
| R | direccion | string ≤ 255 |
| R | numeroCasa | string | int |
| R | departamento / ciudad | int + su …Descripcion |
| O | distrito | int + distritoDescripcion |
| O | lugarTransaccion | objeto con direccion, departamento, ciudad… — si se omite, se asume la dirección del vendedor |
cliente
| Campo | Tipo / valores | |
|---|---|---|
| R | contribuyente | bool |
| R | tipoOperacion | int 1 B2B, 2 B2C, 3 B2G, 4 B2F |
| R | pais / paisDescripcion | PRY / PARAGUAY |
| R | razonSocial | string |
| C | ruc | 1234567-8 — si contribuyente = true |
| C | tipoContribuyente | int 1 | 2 — si contribuyente = true |
| C | TipoDocumentoIdentidad | int 1–6 — si contribuyente = false (N8) |
| C | NumeroDocumentoIdentidad | string — si contribuyente = false (N8) |
| O | nombreFantasia | string |
| O | direccion / numeroCasa | string |
| O | departamento / distrito / ciudad | int + su …Descripcion |
| O | telefono / celular / email / codigo | string (codigo = código externo del ERP) |
compraPublica — obligatorio solo si cliente.tipoOperacion = 3 (B2G, N14)
| Campo | Tipo / valores | |
|---|---|---|
| R | modalidad | string(2) — código de modalidad de la DNCP |
| R | entidad | string(5) — código de entidad de la DNCP |
| R | anio | string(2) — año del código de contratación |
| R | secuencia | string(7) — secuencia del código de contratación |
| R | fechaCodigo | YYYY-MM-DD — fecha de emisión del código de contratación (no posterior a fecha) |
Estos 5 datos los emite la Dirección Nacional de Contrataciones Públicas (DNCP) para el proceso de compra; sin ellos SIFEN rechaza la Factura a un organismo del Estado (código 1400 del Manual Técnico SIFEN). No enviar este bloque si tipoOperacion es 1, 2 o 4.
condicion
| Campo | Tipo / valores | |
|---|---|---|
| R | tipo | int 1 Contado, 2 Crédito |
| C | contado[] | si tipo = 1 — lista de pagos: tipo (1–16), monto, moneda, infoTarjeta [O], infoCheque [O] |
| C | credito | si tipo = 2 — iCondCred (1 Plazo, 2 Cuota), dDCondCred, dPlazoCre, dCuotas, dMonEnt, cuotas[] |
Si tipo = 2 sin bloque credito, la API inyecta { iCondCred: 1, dDCondCred: "Plazo", dPlazoCre: "30 dias" }. En infoTarjeta: si la procesadora no tiene RUC, "ruc": "-"; si tipo = 99, agregar tipoDescripcion.
items[] — al menos 1
| Campo | Tipo / valores | |
|---|---|---|
| R | codigo / descripcion | string |
| R | cantidad | numérico |
| C | precioUnitario | numérico — no enviar si tipoDocumento = 7 |
| R | pais / paisDescripcion | PRY / PARAGUAY |
| O | unidadMedida / unidadMedidaDesc | int (77 = UNI) / string |
| O | ncm / partidaArancelaria / observacion | string |
| C | descuento / descuentoPorcentaje / descuentoGlobal | numérico |
| C | anticipo / anticipoGlobal | numérico |
| C | totalItem | numérico (= precioUnitario × cantidad − dtos) |
| C | ivaTipo | int 1 Gravado, 2 Exonerado, 3 Exento, 4 Grav. parcial |
| C | ivaBase | int 0–100 (proporción gravada; 100 = todo) |
| C | iva | int 0 | 5 | 10 (tasa) |
| O | basGravIVA / liqIVAItem | la API los recalcula (N5); lo que mandes se ignora |
C = obligatorio salvo en la Nota de Remisión (tipoDocumento 7), que describe la mercadería sin precio ni IVA: ahí no enviar precioUnitario / descuento* / anticipo* / totalItem / ivaTipo / ivaBase / iva. Para el resto de los tipos son obligatorios.
totales, notaCreditoDebito
| Campo | Tipo / valores | |
|---|---|---|
| C | totales | los 8 campos de N6, todos (0 si no hay) — no se envía si tipoDocumento = 7 |
| O | notaCreditoDebito.motivo | int 1–8 (default 1) — si tipoDocumento 5 o 6 |
documentoAsociado — obligatorio si tipoDocumento 5 o 6 (N9); opcional para el resto
Referencia libre a otro comprobante. El campo formato decide qué otros campos van:
| Campo | Tipo / valores | |
|---|---|---|
| R | formato | int 1 Electrónico, 2 Impreso, 3 Constancia Electrónica |
| C | cdc | 44 dígitos — si formato = 1 |
| C | timbrado / establecimiento / punto / numero / fecha | string(8) / (3) / (3) / (7) / YYYY-MM-DD — si formato = 2 |
| C | tipoDocumentoImpreso | int 1 Factura, 2 NC, 3 ND, 4 Nota de remisión, 5 Comprobante de retención — si formato = 2 |
| C | tipoConstancia | int 1 No ser contribuyente, 2 Microproductores — si formato = 3 (típico en Autofactura) |
| C | numeroConstancia / numeroControl | string(11) / string(8) — si tipoConstancia = 2 |
remision — obligatorio solo si tipoDocumento = 7 (N12)
| Campo | Tipo / valores | |
|---|---|---|
| R | motivo | int 1–14 | 99 — motivo del traslado |
| R | responsable | int 1–5 — responsable de emitir la NRE |
| O | kmRecorrido | numérico |
| O | fechaEmisionFactura | YYYY-MM-DD — factura que respalda el traslado |
transporte — obligatorio solo si tipoDocumento = 7 (N12)
| Campo | Tipo / valores | |
|---|---|---|
| R | tipoTransporte | int 1 Propio, 2 Tercero |
| R | modalidad | int 1 Terrestre, 2 Fluvial, 3 Aéreo, 4 Multimodal |
| R | responsableFlete | int 1–5 |
| R | fechaInicioTraslado / fechaFinTraslado | YYYY-MM-DD (fin ≥ inicio) |
| R | localSalida / localEntrega | objeto: direccion, numeroCasa, departamento, ciudad [R] · distrito, telefono [O] |
| R | vehiculo | objeto: tipo ≤10 (ej. «CAMION»), marca ≤10, tipoIdentificacion (1 Nro. identificación, 2 Nro. matrícula/chapa) + numeroIdentificacion o numeroMatricula según corresponda; numeroVuelo si modalidad = 3 |
| O | transportista | objeto: naturaleza (1 Contribuyente, 2 No contrib.), nombre, ruc o tipoDocumentoIdentidad/numeroDocumentoIdentidad según naturaleza, choferDocumentoIdentidad [R], choferNombre [R], direccionChofer [O] |
05 Ejemplos
Factura electrónica al contado — el caso más común
// Headers: Content-Type: application/json · Accept: application/json // Authorization: Bearer <TOKEN> · Idempotency-Key: FE-001-001-0000001 { "recordID": "5F-7111E5F9", "tipoDocumento": 1, "tipoEmision": 1, "establecimiento": "001", "punto": "001", "numero": "0000001", "fecha": "2026-05-27T10:15:00", "moneda": "PYG", "tipoImpuesto": 1, "tipoTransaccion": 1, "condicionAnticipo": 1, "descripcion": "Venta mostrador", "factura": { "presencia": 1 }, "cliente": { "contribuyente": false, "tipoOperacion": 2, "pais": "PRY", "paisDescripcion": "PARAGUAY", "razonSocial": "DUARTE GONZALEZ, DERLIS ARIEL", "TipoDocumentoIdentidad": 1, "NumeroDocumentoIdentidad": "4829212" }, "condicion": { "tipo": 1, "contado": [ { "tipo": 1, "monto": 45000, "moneda": "PYG" } ] }, "items": [ { "codigo": "1", "descripcion": "CARNAZA DE PRIMERA", "unidadMedida": 77, "unidadMedidaDesc": "UNI", "cantidad": 1, "precioUnitario": 45000, "pais": "PRY", "paisDescripcion": "PARAGUAY", "descuento": 0, "descuentoPorcentaje": 0, "descuentoGlobal": 0, "anticipo": 0, "anticipoGlobal": 0, "totalItem": 45000, "ivaTipo": 1, "ivaBase": 100, "iva": 10, "basGravIVA": 40910, "liqIVAItem": 4090 } ], "totales": { "dTotDesc": 0, "dTotDescGlotem": 0, "dTotAntItem": 0, "dTotAnt": 0, "dPorcDescTotal": 0, "dDescTotal": 0, "dAnticipo": 0, "dRedon": 0 } }
{
"success": true,
"cdc": "01042214610001001000000112026052717799105109",
"record_id": "5F-7111E5F9",
"numero_completo": "001-001-0000001",
"estado": "enviado",
"estado_sifen": "Pendiente",
"mensaje_sifen": "Lote recibido por SIFEN. Pendiente de consulta automatica.",
"nro_lote": "1234567890",
"qr_url": "https://ekuatia.set.gov.py/consultas/qr?nVersion=150&Id=...",
"links": {
"xml": "{BASE_URL}/api/v1/documentos/01042214.../xml",
"json": "{BASE_URL}/api/v1/documentos/01042214.../json",
"kude": "{BASE_URL}/api/v1/documentos/01042214.../kude?format=pdf",
"qr": "{BASE_URL}/api/v1/documentos/01042214.../qr"
},
"documento": { /* recurso completo */ }
}
Equivalente por la ruta legacy — siempre HTTP 200
{
"respuesta_sifen": { "dCodRes": "0300", "dMsgRes": "Lote recibido con exito",
"dProtConsLote": "1234567890" },
"cdc": "01042214610001001000000112026052717799105109",
"nro_lote": "1234567890",
"estado_sifen": "Pendiente",
"mensaje_sifen": "Lote recibido por SIFEN. Pendiente de consulta automatica."
}
Cliente contribuyente (con RUC) y varios ítems, uno exento
"cliente": { "contribuyente": true, "tipoOperacion": 1, "pais": "PRY", "paisDescripcion": "PARAGUAY", "ruc": "80012345-6", "tipoContribuyente": 2, "razonSocial": "CONSTRUCTORA DEL ESTE S.A.", "nombreFantasia": "CONESA", "email": "compras@conesa.com.py", "codigo": "CLI-000123" }, "items": [ { "codigo": "CEM-50", "descripcion": "CEMENTO PORTLAND 50KG", "cantidad": 10, "precioUnitario": 55000, "totalItem": 550000, "pais": "PRY", "paisDescripcion": "PARAGUAY", "descuento": 0, "descuentoPorcentaje": 0, "descuentoGlobal": 0, "anticipo": 0, "anticipoGlobal": 0, "ivaTipo": 1, "ivaBase": 100, "iva": 10, "basGravIVA": 500000, "liqIVAItem": 50000 }, { "codigo": "LIB-01", "descripcion": "LIBRO TECNICO (EXENTO)", "cantidad": 1, "precioUnitario": 80000, "totalItem": 80000, "pais": "PRY", "paisDescripcion": "PARAGUAY", "descuento": 0, "descuentoPorcentaje": 0, "descuentoGlobal": 0, "anticipo": 0, "anticipoGlobal": 0, "ivaTipo": 3, "ivaBase": 0, "iva": 0, "basGravIVA": 0, "liqIVAItem": 0 } ] // La API calcula: dSub10 = 550000 dSubExe = 80000 dTotGralOpe = 630000 // dIVA10 = 50000 dTotIVA = 50000 dBaseGrav10 = 500000
Nota de crédito electrónica
{
"recordID": "5F-7111E5F9",
"tipoDocumento": 5,
"tipoEmision": 1,
"establecimiento": "001", "punto": "001", "numero": "0000045",
"fecha": "2026-06-02T10:15:00",
"moneda": "PYG", "tipoImpuesto": 1, "condicionAnticipo": 1,
"notaCreditoDebito": { "motivo": 2 },
"documentoAsociado": {
"formato": 1,
"cdc": "01042214610001001000000112026052717799105109"
},
"cliente": { /* igual que en la factura original */ },
"condicion": { "tipo": 1,
"contado": [ { "tipo": 1, "monto": 45000, "moneda": "PYG" } ] },
"items": [ /* los items que se devuelven, con su IVA */ ],
"totales": { "dTotDesc": 0, "dTotDescGlotem": 0, "dTotAntItem": 0,
"dTotAnt": 0, "dPorcDescTotal": 0, "dDescTotal": 0,
"dAnticipo": 0, "dRedon": 0 }
}
// Para NOTA DE DEBITO: tipoDocumento = 6 (el resto idéntico). En NC/ND no se envía "factura".
Autofactura Electrónica (tipoDocumento 4)
Compra a un proveedor no contribuyente. El receptor (cliente) es el propio emisor; vendedor identifica a quién se le compró. No se envía factura.
{
"recordID": "5F-7111E5F9",
"tipoDocumento": 4,
"tipoEmision": 1,
"establecimiento": "001", "punto": "001", "numero": "0000010",
"fecha": "2026-06-03T09:00:00",
"moneda": "PYG", "tipoImpuesto": 1, "tipoTransaccion": 10,
"condicionAnticipo": 1,
"cliente": {
"contribuyente": true,
"tipoOperacion": 1,
"pais": "PRY", "paisDescripcion": "PARAGUAY",
"ruc": "3010353-3",
"tipoContribuyente": 1,
"razonSocial": "DANIA SOFIA CORONEL NOGUERA"
},
"vendedor": {
"naturaleza": 1,
"tipoDocumentoIdentidad": 1,
"numeroDocumentoIdentidad": "2345678",
"nombre": "JUAN PEREZ",
"direccion": "RUTA 2 KM 45", "numeroCasa": "0",
"departamento": 5, "departamentoDescripcion": "GUAIRA",
"ciudad": 2657, "ciudadDescripcion": "VILLARRICA"
},
"condicion": { "tipo": 1,
"contado": [ { "tipo": 1, "monto": 150000, "moneda": "PYG" } ] },
"items": [
{ "codigo": "MAIZ", "descripcion": "MAIZ A GRANEL",
"unidadMedida": 79, "unidadMedidaDesc": "kg",
"cantidad": 100, "precioUnitario": 1500,
"pais": "PRY", "paisDescripcion": "PARAGUAY",
"totalItem": 150000,
"ivaTipo": 1, "ivaBase": 100, "iva": 10 }
],
"totales": { "dTotDesc": 0, "dTotDescGlotem": 0, "dTotAntItem": 0, "dTotAnt": 0,
"dPorcDescTotal": 0, "dDescTotal": 0, "dAnticipo": 0, "dRedon": 0 },
"documentoAsociado": { "formato": 3, "tipoConstancia": 1 }
}
// "cliente.ruc" / "cliente.razonSocial" son los del PROPIO emisor. El CDC de una Autofactura empieza con "04".
Nota de Remisión Electrónica (tipoDocumento 7)
Ampara un traslado de mercadería. Sin factura, sin condicion, sin totales, sin tipoImpuesto/condicionAnticipo. Los ítems no llevan precio ni IVA.
{
"recordID": "5F-7111E5F9",
"tipoDocumento": 7,
"tipoEmision": 1,
"establecimiento": "001", "punto": "001", "numero": "0000011",
"fecha": "2026-06-04T07:30:00",
"moneda": "PYG",
"cliente": {
"contribuyente": true, "tipoOperacion": 1,
"pais": "PRY", "paisDescripcion": "PARAGUAY",
"ruc": "80012345-6", "tipoContribuyente": 2,
"razonSocial": "CONSTRUCTORA DEL ESTE S.A."
},
"remision": { "motivo": 1, "responsable": 1, "kmRecorrido": 180,
"fechaEmisionFactura": "2026-06-04" },
"transporte": {
"tipoTransporte": 1,
"modalidad": 1,
"responsableFlete": 5,
"fechaInicioTraslado": "2026-06-04",
"fechaFinTraslado": "2026-06-04",
"localSalida": {
"direccion": "DEPOSITO CENTRAL", "numeroCasa": "0",
"departamento": 5, "departamentoDescripcion": "GUAIRA",
"ciudad": 2657, "ciudadDescripcion": "VILLARRICA"
},
"localEntrega": {
"direccion": "OBRA AVDA. MCAL. LOPEZ 1234", "numeroCasa": "1234",
"departamento": 1, "departamentoDescripcion": "CAPITAL",
"ciudad": 1, "ciudadDescripcion": "ASUNCION"
},
"vehiculo": {
"tipo": "CAMION", "marca": "HINO",
"tipoIdentificacion": 2, "numeroMatricula": "ABC123"
},
"transportista": {
"naturaleza": 1, "nombre": "FLETES DEL SUR SRL", "ruc": "80099999-0",
"choferDocumentoIdentidad": "1234567", "choferNombre": "PEDRO GOMEZ"
}
},
"items": [
{ "codigo": "CEM-50", "descripcion": "CEMENTO PORTLAND 50KG",
"unidadMedida": 77, "unidadMedidaDesc": "UNI",
"cantidad": 200,
"pais": "PRY", "paisDescripcion": "PARAGUAY" }
]
}
// El CDC de una Nota de Remision empieza con "07". Puede quedar "Pendiente" igual que una factura; el KUDE se imprime para acompañar la carga.
Venta al Estado / B2G — ej. un ministerio (N14)
Factura Electrónica normal (tipoDocumento 1), pero el receptor es una entidad de gobierno: cliente.tipoOperacion = 3, y es obligatorio el bloque compraPublica con los datos del código de contratación (DNCP).
{
"recordID": "5F-7111E5F9",
"tipoDocumento": 1,
"tipoEmision": 1,
"establecimiento": "001", "punto": "001", "numero": "0000012",
"fecha": "2026-06-05T10:00:00",
"moneda": "PYG", "tipoImpuesto": 1, "tipoTransaccion": 1,
"condicionAnticipo": 1,
"factura": { "presencia": 1 },
"cliente": {
"contribuyente": true,
"tipoOperacion": 3,
"pais": "PRY", "paisDescripcion": "PARAGUAY",
"ruc": "80000000-2",
"tipoContribuyente": 2,
"razonSocial": "MINISTERIO DE EDUCACION Y CIENCIAS (EJEMPLO)"
},
"compraPublica": {
"modalidad": "LC",
"entidad": "00001",
"anio": "26",
"secuencia": "0000001",
"fechaCodigo": "2026-05-20"
},
"condicion": { "tipo": 1,
"contado": [ { "tipo": 1, "monto": 550000, "moneda": "PYG" } ] },
"items": [
{ "codigo": "SERV-01", "descripcion": "MATERIAL DIDACTICO",
"unidadMedida": 77, "unidadMedidaDesc": "UNI",
"cantidad": 1, "precioUnitario": 500000,
"pais": "PRY", "paisDescripcion": "PARAGUAY",
"totalItem": 500000,
"ivaTipo": 1, "ivaBase": 100, "iva": 10 }
],
"totales": { "dTotDesc": 0, "dTotDescGlotem": 0, "dTotAntItem": 0, "dTotAnt": 0,
"dPorcDescTotal": 0, "dDescTotal": 0, "dAnticipo": 0, "dRedon": 0 }
}
// El RUC del ministerio y los datos de "compraPublica" son de EJEMPLO: usá el RUC real del organismo y el código de contratación que te entregó la DNCP.
06 Respuestas HTTP
API nueva — POST /api/v1/documentos
success, cdc, estado, estado_sifen, nro_lote, qr_url, links, documento.Idempotency-Key: devuelve el documento ya emitido, mismo cuerpo que el 201.{ "message": "...", "errors": { campo: [msgs] } } — mensajes en español. También si el tipoDocumento no está habilitado para tu empresa (N13), si en una Autofactura el cliente no es el propio emisor (N11), o si falta compraPublica en una venta al Estado (N14).{ "success": false, "message": "..." }Idempotency-Key. El cuerpo trae el documento existente (con su cdc).cdc y warning.Authorization o el token no es válido.API legacy — POST /api-py/firmar-enviar.php
Siempre HTTP 200. El resultado se lee en el cuerpo:
| Caso | Cuerpo |
|---|---|
| OK | { respuesta_sifen, cdc, nro_lote, estado_sifen, mensaje_sifen } |
| Error de envío | { success:false, cdc, xml, warning:"Envio SIFEN fallido.", estado_sifen:"Rechazado", mensaje_sifen } |
| Error general | { success:false, estado_sifen:"ERROR", mensaje_sifen } |
?modo=local | { success:true, cdc, xml, nro_lote:"", estado_sifen:"Pendiente", mensaje_sifen } |
Estados posibles (estado_sifen): Pendiente · Aprobado · Aprobado con observacion · Rechazado · ERROR.
07 Idempotencia — cabecera Idempotency-Key
Recomendada siempre en la API nueva.
Idempotency-Key: <clave-única-del-comprobante> (≤ 100 chars)"FE-" + establecimiento + "-" + punto + "-" + numero — o el ID interno del comprobante en tu ERP- 1.er POST con esa clave → emite normal (
201). - 2.º POST con la misma clave y el mismo JSON → no duplica: devuelve el documento ya emitido con
HTTP 200(mismocdc). - Sin
Idempotency-Key, repetir el número da409.
Uso típico: ante un timeout de red, reintentás el mismo request con la misma clave y recuperás el CDC sin arriesgarte a emitir dos veces. La ruta legacy no usa la cabecera: repetir el mismo número devuelve igual (200) el documento ya emitido.
08 Consultar el estado
Al emitir casi siempre vuelve Pendiente: SIFEN procesa el lote de forma asíncrona. La API consulta sola el estado final con reintentos a 5 s, 15 s, 1 min, 5 min, 15 min y 1 h. El comprobante ya es válido para entregar al cliente (el KUDE se puede imprimir) aunque esté Pendiente.
El sistema emisor puede consultar cuando quiera:
| Método | Ruta | Para |
|---|---|---|
| GET | /api/v1/documentos/{cdc} | estado actualizado |
| GET | /api/v1/lotes/{nroLote} | estado del lote |
| GET | /api-py/consultar.php?lote={n} | versión legacy |
| POST | /api/v1/documentos/{cdc}/consultar | fuerza la consulta ya |
09 Descargar XML / JSON / QR / KUDE
| Ruta | Devuelve |
|---|---|
/api/v1/documentos/{cdc}/xml | text/xml |
/api/v1/documentos/{cdc}/json | application/json |
/api/v1/documentos/{cdc}/qr | { "success": true, "qr": "https://..." } |
/api/v1/documentos/{cdc}/kude?format=pdf | application/pdf |
/api/v1/documentos/{cdc}/kude?format=html | text/html |
Legacy: get_xml.php?cdc= · get_json.php?cdc= · get_qr.php?cdc= · kude.php?cdc=&format=pdf.
El KUDE en PDF ya trae el QR embebido. Nunca reconstruir el QR por cuenta propia: usar el PDF de la API o el qr_url que devuelve la emisión.
10 Anular un documento
POST {BASE_URL}/api/v1/documentos/{cdc}/anular
// Header: Authorization: Bearer <TOKEN>
{ "motivo": "Error en los datos del cliente" }POST {BASE_URL}/api-py/anular.php
{ "id": 1, "cdc": "0104...", "motivo": "Error en los datos del cliente" }{ "success": true, "estado_sifen": "Aprobado", "codigo_sifen": "0600",
"mensaje_sifen": "Evento registrado con exito", "respuesta_sifen": { /* ... */ } }
Reglas SIFEN: el motivo debe tener entre 5 y 500 caracteres; la anulación se acepta dentro de las 48 horas de la emisión.
10.1 Reenviar (reintentar el envío de un documento ya firmado)
Se usa cuando el documento ya tiene cdc (quedó firmado) pero el envío a SIFEN falló o quedó en un estado no final — por ejemplo recibiste un 502, o el estado_sifen quedó en Rechazado/ERROR por un problema de red (no por un rechazo de negocio de SIFEN). Reenviar no genera un CDC nuevo ni cambia el número: reutiliza el XML ya firmado tal cual.
POST {BASE_URL}/api/v1/documentos/{cdc}/reenviar
// Header: Authorization: Bearer <TOKEN>
// (sin body)Respuesta: mismo formato que POST /api/v1/documentos (success, cdc, estado, estado_sifen, nro_lote, qr_url, links, documento). 200 si SIFEN aceptó el lote, 502 si volvió a fallar (el documento sigue firmado; se puede reintentar más tarde o forzar una consulta).
Si el documento ya está aprobado o anulado el reenvío no aplica → 422 { "message": "El documento ya esta aprobado; no se puede reenviar." }
Diferencia con la Idempotencia: la cabecera Idempotency-Key es para cuando el POST /api/v1/documentos ORIGINAL no responde (timeout de red) — ahí todavía no sabés si la API llegó a procesar el JSON, así que reintentás el mismo POST con el mismo número. /reenviar es para cuando ya tenés confirmado el cdc (el documento SÍ se creó y firmó) pero el envío de ESE documento a SIFEN falló: no hace falta mandar el JSON de nuevo, solo el CDC en la URL.
API legacy: no tiene endpoint de reenvío. Usá el panel de administración (botón "Reenviar" en el detalle del documento) o el reintento con Idempotency-Key.
11 Manejo de errores en el sistema emisor
- Guardá siempre el
cdcque devuelve la API, aunqueestado_sifenseaRechazadooERROR: ese CDC ya quedó asignado a ese número y no se puede reutilizar. - Si no hay respuesta (timeout): no generes número nuevo. Reintentá el mismo JSON con la misma
Idempotency-Key. - Si
estado_sifen = "Rechazado": leémensaje_sifenycodigo_sifen, corregí y emití un comprobante nuevo con número nuevo (un DE rechazado no se corrige, se reemplaza). - Si
estado_sifen = "Pendiente": es lo normal. Entregá el comprobante / imprimí el KUDE. Consultá el estado final más tarde. 429: esperá y reintentá.502: el CDC ya existe. Guardalo. La API reintenta el envío sola cada 10 minutos (hasta 5 intentos); si no querés esperar, forzá el reenvío ya mismo conPOST .../reenviar, o consultá el estado conPOST .../consultar.
12 Código de ejemplo
Reemplazá {BASE_URL} y <TOKEN>.
curl -X POST {BASE_URL}/api/v1/documentos \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-H "Idempotency-Key: FE-001-001-0000001" \
--data-binary "@factura.json"
# En Windows (cmd): cambiar \ por ^$base = getenv('SIFEN_API_URL'); // https://facturacion.miempresa.com $token = getenv('SIFEN_API_TOKEN'); $json = json_encode($factura, JSON_UNESCAPED_UNICODE); $ch = curl_init(rtrim($base, '/') . '/api/v1/documentos'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $json, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 90, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json', 'Authorization: Bearer ' . $token, 'Idempotency-Key: FE-' . $est . '-' . $punto . '-' . $numero, ], ]); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $resp = json_decode($body, true); if (!empty($resp['cdc'])) { // guardar $resp['cdc'] y $resp['nro_lote'] en el ERP }
var baseUrl = Environment.GetEnvironmentVariable("SIFEN_API_URL")!.TrimEnd('/'); using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) }; http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); http.DefaultRequestHeaders.Add("Idempotency-Key", $"FE-{est}-{pto}-{nro}"); var content = new StringContent( JsonSerializer.Serialize(factura), Encoding.UTF8, "application/json"); var resp = await http.PostAsync($"{baseUrl}/api/v1/documentos", content); var body = await resp.Content.ReadAsStringAsync();
import os, json, requests base = os.environ["SIFEN_API_URL"].rstrip("/") r = requests.post( f"{base}/api/v1/documentos", headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json", "Idempotency-Key": f"FE-{est}-{pto}-{nro}"}, data=json.dumps(factura, ensure_ascii=False).encode("utf-8"), timeout=90) print(r.status_code, r.json())
// El sistema actual sigue funcionando: solo cambia el host. Http.Request.ContentType := 'application/json'; Http.Request.CharSet := 'utf-8'; Http.ConnectTimeout := 15000; Http.ReadTimeout := 90000; Body := TStringStream.Create(JsonFactura, TEncoding.UTF8); Resp := Http.Post(BaseUrl + '/api-py/firmar-enviar.php', Body); // Resp trae: cdc, nro_lote, estado_sifen, mensaje_sifen // BaseUrl es una CONSTANTE de configuración: cambiar solo eso al migrar. // La ruta /api-py/firmar-enviar.php y el formato de la respuesta NO cambian.
13 Tablas de códigos SIFEN
Los textos van tal cual; respetar tildes.
| Campo | Valores |
|---|---|
tipoDocumento | 1 = Factura · 4 = Autofactura · 5 = Nota de crédito · 6 = Nota de débito · 7 = Nota de remisión |
tipoEmision | 1 = Normal · 2 = Contingencia |
tipoTransaccion | 1 Venta de mercadería · 2 Prestación de servicios · 3 Mixto · 4 Venta de activo fijo · 5 Venta de divisas · 6 Compra de divisas · 7 Promoción/muestras · 8 Donación · 9 Anticipo · 10 Compra de productos · 11 Compra de servicios · 12 Venta de crédito fiscal · 13 Muestras médicas |
tipoImpuesto | 1 IVA · 2 ISC · 3 Renta · 4 Ninguno · 5 IVA-Renta |
condicionAnticipo | 1 Anticipo Global · 2 Anticipo por Ítem |
presencia | 1 Presencial · 2 Electrónica · 3 Telemarketing · 4 A domicilio · 5 Bancaria · 6 Cíclica |
condicion.tipo | 1 Contado · 2 Crédito |
contado.tipo (iTiPago) | 1 Efectivo · 2 Cheque · 3 Tarjeta crédito · 4 Tarjeta débito · 5 Transferencia · 6 Giro · 7 Billetera electrónica · 8 Tarjeta empresarial · 9 Vale · 10 Retención · 11 Pago por anticipo · 12 Valor fiscal · 13 Valor comercial · 14 Compensación · 15 Permuta · 16 Pago bancario |
infoTarjeta.tipo | 1 Visa · 2 Mastercard · 3 American Express · 4 Maestro · 5 Panal · 6 Cabal · 99 Otra (mandar tipoDescripcion) |
infoTarjeta.medioPago | 1 POS · 2 Pago Electrónico · 3 Pago Móvil · 4 Otro |
item.ivaTipo | 1 Gravado IVA · 2 Exonerado · 3 Exento · 4 Gravado parcial |
TipoDocumentoIdentidad | 1 Cédula paraguaya · 2 Pasaporte · 3 Cédula extranjera · 4 Carnet de residencia · 5 Innominado · 6 Tarjeta Diplomática de exoneración fiscal |
cliente.tipoOperacion | 1 B2B · 2 B2C · 3 B2G · 4 B2F |
cliente.tipoContribuyente | 1 Persona Física · 2 Persona Jurídica |
notaCreditoDebito.motivo | 1 Devolución y ajuste de precios · 2 Devolución · 3 Descuento · 4 Bonificación · 5 Crédito incobrable · 6 Recupero de costo · 7 Recupero de gasto · 8 Ajuste de precio |
credito.iCondCred | 1 Plazo · 2 Cuota |
documentoAsociado.formato | 1 Electrónico · 2 Impreso · 3 Constancia Electrónica |
documentoAsociado.tipoDocumentoImpreso (formato 2) | 1 Factura · 2 Nota de crédito · 3 Nota de débito · 4 Nota de remisión · 5 Comprobante de retención |
documentoAsociado.tipoConstancia (formato 3) | 1 Constancia de no ser contribuyente · 2 Constancia de microproductores |
unidadMedida | 77 UNI · 79 kg · 83 g · 86 LT · 87 ML · 89 m · 90 m² · 91 m³ · 101 Hora · 104 Día · 108 Mes · 110 Año |
moneda | PYG Guaraní · USD Dólar Americano · BRL Real · ARS Peso Argentino · EUR Euro (para PYG la API escribe «Guarani») |
pais (ISO alfa-3) | PRY PARAGUAY · ARG ARGENTINA · BRA BRASIL · URY URUGUAY · CHL CHILE · BOL BOLIVIA · USA ESTADOS UNIDOS |
Autofactura Electrónica — tipoDocumento 4
vendedor.naturaleza | 1 No contribuyente · 2 Extranjero |
vendedor.tipoDocumentoIdentidad | 1 Cédula paraguaya · 2 Pasaporte · 3 Cédula extranjera · 4 Carnet de residencia |
Nota de Remisión Electrónica — tipoDocumento 7
remision.motivo | 1 Traslado por venta · 2 Traslado por consignación · 3 Exportación · 4 Traslado por compra · 5 Importación · 6 Traslado por devolución · 7 Traslado entre locales de la empresa · 8 Traslado por transformación · 9 Traslado por reparación · 10 Traslado por emisor móvil · 11 Exhibición o demostración · 12 Participación en ferias · 13 Traslado de encomienda · 14 Decomiso · 99 Otro |
remision.responsable | 1 Emisor de la factura · 2 Poseedor de la factura y bienes · 3 Empresa transportista · 4 Despachante de Aduanas · 5 Agente de transporte o intermediario |
transporte.tipoTransporte | 1 Propio · 2 Tercero |
transporte.modalidad | 1 Terrestre · 2 Fluvial · 3 Aéreo · 4 Multimodal |
transporte.responsableFlete | 1 Emisor de la FE · 2 Receptor de la FE · 3 Tercero · 4 Agente intermediario del transporte · 5 Transporte propio |
vehiculo.tipoIdentificacion | 1 Nro. de identificación del vehículo · 2 Nro. de matrícula/chapa del vehículo |
transportista.naturaleza | 1 Contribuyente · 2 No contribuyente |
Códigos de respuesta SIFEN más comunes
0300 | Lote recibido con éxito | 0301 | Lote en proceso |
0302 | Lote procesado | 0362 | Consulta de lote exitosa |
0420 | CDC ya existente | 0421 | CDC inexistente |
0600 | Evento registrado | 0601 | Evento rechazado |
2xxx / 3xxx — errores de validación de campos del DE | |||
14 Checklist de integración
- Tengo el
{BASE_URL}y un token (API nueva) o mi IP está permitida (legacy). - El
{BASE_URL}es una constante de configuración en mi sistema (puede cambiar sin recompilar). - Envío
recordIDen cada request. establecimiento/punto/numerocon 3 / 3 / 7 dígitos y ceros a la izquierda.- No repito números; uso
Idempotency-Key. fechaenYYYY-MM-DDTHH:MM:SS, hora de Paraguay.- Importes como número JSON (sin separador de miles, punto decimal).
- Calculo
basGravIVAyliqIVAItempor ítem (fórmula N5). - Mando el bloque
totalescon los 8 campos del emisor (0 si no hay). - Si contado: la suma de pagos = total del comprobante.
- Guardo el
cdcy elnro_lotede la respuesta siempre. - Manejo
201/200/409/422/403/502/429(o, en legacy, leo el cuerpo). - Imprimo el KUDE desde la API (no reconstruyo el QR).
- Probé en ambiente test antes de producción.
- Si emito Autofactura (4):
cliente= mi propio emisor + bloquevendedor. - Si emito Nota de Remisión (7): bloques
remisionytransporte; ítems sin precio/IVA; sinfactura/condicion/totales. - Autofactura (4) / Nota de Remisión (7): el proveedor las habilitó para mi empresa.
15 Si tu proveedor activó el modo multiempresa (SaaS)
Todo lo de arriba sigue igual (URLs, JSON, Idempotency-Key, capa legacy). Lo que cambia:
El token ahora identifica a la empresa
Antes, el token identificaba solo a un usuario del panel y la empresa se resolvía por el recordID del JSON. Ahora el token en sí mismo identifica a la empresa, y el recordID se valida contra ella: si no coincide, 403 EMPRESA_NO_AUTORIZADA. Los tokens que ya tenías emitidos siguen funcionando sin cambios.
Códigos de error nuevos
Mismo formato de siempre, con un campo codigo además de error:
| HTTP | codigo | Cuándo |
|---|---|---|
| 401 | TOKEN_FALTANTE | Falta la cabecera Authorization. |
| 401 | TOKEN_INVALIDO | Token inexistente o adulterado. |
| 401 | TOKEN_EXPIRADO | Revocado o vencido. |
| 401 | USUARIO_INACTIVO | El usuario dueño del token está desactivado. |
| 403 | EMPRESA_NO_AUTORIZADA | El recordID no es el de la empresa del token. |
| 403 | EMPRESA_NO_OPERATIVA | Empresa pendiente, cerrada o purgada. |
| 403 | MODULO_NO_CONTRATADO | Tipo de documento o función no contratada (incluye api_rest: ver abajo). |
| 402 | SUSCRIPCION_VENCIDA | Vencida la gracia: no se puede emitir. |
| 402 | LIMITE_DOCUMENTOS | Cupo mensual agotado (trae detalle: {usado, limite, periodo}). |
| 503 | EMPRESA_EN_MANTENIMIENTO | Migración o purga en curso. |
Cabeceras nuevas en la emisión
X-Empresa-Slug, X-Cupo-Usado, X-Cupo-Limite, X-Suscripcion-Vence.
Endpoint nuevo
GET {BASE_URL}/api/v1/cuenta (solo lectura, no consume cupo) → { empresa, ruc, estado, plan, modulos[], vence_at, ambiente, produccion_habilitada }.
El acceso por API es, a su vez, un módulo contratado (api_rest)
Emitir por /api/v1 es uno de los dos canales de emisión (el otro es el formulario del panel, módulo panel_facturacion) y cada uno se contrata por separado. Si tu empresa no tiene api_rest, POST /api/v1/documentos, .../anular y .../reenviar responden 403 MODULO_NO_CONTRATADO:
{
"success": false,
"error": "El modulo 'api_rest' no esta contratado.",
"codigo": "MODULO_NO_CONTRATADO"
}No es un error transitorio: no reintentes, avisale al usuario. Con el mismo token, esto sigue funcionando siempre, tenga o no el módulo: GET /api/v1/salud, GET /api/v1/cuenta, el listado y el detalle de documentos, POST .../consultar y las descargas de XML/JSON/QR/KUDE — lo ya emitido se consulta y se descarga siempre.
Guía completa para integraciones nuevas contra el SaaS: MANUAL_FOR_SAAS.md (en el repositorio de la API).
Novedad v1.5 — módulo de revendedores
Si tu proveedor además activó el módulo de revendedores, un revendedor puede dar de alta y administrar sus propias empresas clientes bajo marca propia, con panel y guard totalmente separados. Esto no cambia en nada el contrato de esta página: una empresa dada de alta por un revendedor que tenga el módulo api_rest integra exactamente igual que cualquier otra (mismo JSON, mismos endpoints /api/v1). Ver MANUAL_REVENDEDOR.md si vos mismo operás como revendedor.
Contrato v1.5 · Formato SIFEN rDE v150 · Este documento es el contrato público de integración.